From 12fb2fab5676fc3ee516b4eb695da0ffc204eeda Mon Sep 17 00:00:00 2001 From: Mridula Date: Mon, 2 Jun 2025 21:00:49 +0100 Subject: [PATCH 001/102] propgating retrievers to inner retrievers --- .../elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java index 436096523a1ec..6e94573fe065c 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java @@ -199,6 +199,7 @@ public void doToXContent(XContentBuilder builder, Params params) throws IOExcept builder.field(LinearRetrieverComponent.WEIGHT_FIELD.getPreferredName(), weights[index]); builder.field(LinearRetrieverComponent.NORMALIZER_FIELD.getPreferredName(), normalizers[index].getName()); builder.endObject(); + entry.retriever.minScore(this.minScore); index++; } builder.endArray(); From 81e99b6d099856570591b88bca118e802e06fe0f Mon Sep 17 00:00:00 2001 From: Mridula Date: Fri, 6 Jun 2025 11:37:27 +0100 Subject: [PATCH 002/102] test feature taken care of --- .../rest-api-spec/test/linear/10_linear_retriever.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml index 61c8c82fb6046..0f3d2167c14c1 100644 --- a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml +++ b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml @@ -266,6 +266,10 @@ setup: --- "should normalize initial scores with l2_norm": + - skip: + version: "<8.19.0" + reason: "l2_norm normalization is only available from 8.19.0" + features: "l2_norm" - do: search: index: test @@ -316,9 +320,14 @@ setup: - close_to: { hits.hits.2._score: { value: 1.6, error: 0.001 } } - match: { hits.hits.3._id: "3" } - close_to: { hits.hits.3._score: { value: 1.2, error: 0.001} } + - close_to: { hits.hits.3._score: { value: 1.2, error: 0.001 } } --- "should handle all zero scores in normalization": + - skip: + version: "<8.19.0" + reason: "l2_norm normalization is only available from 8.19.0" + features: "l2_norm" - do: search: index: test From 605c03549ff5c9d2f5a76be60dd7d31f71734ce3 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Fri, 6 Jun 2025 16:58:39 +0200 Subject: [PATCH 003/102] Small changes in concurrent multipart upload interfaces (#128977) Small changes in BlobContainer interface and wrapper. Relates ES-11815 --- .../azure/AzureBlobContainer.java | 3 +- .../repositories/azure/AzureBlobStore.java | 5 ++- .../common/blobstore/BlobContainer.java | 34 +++++++++++++++++-- .../common/blobstore/fs/FsBlobContainer.java | 4 +++ .../support/FilterBlobContainer.java | 16 +++++++++ 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobContainer.java b/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobContainer.java index a040067d7b1b0..ab8e10ce9de27 100644 --- a/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobContainer.java +++ b/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobContainer.java @@ -15,7 +15,6 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.common.CheckedBiFunction; import org.elasticsearch.common.Strings; import org.elasticsearch.common.blobstore.BlobContainer; import org.elasticsearch.common.blobstore.BlobPath; @@ -116,7 +115,7 @@ public void writeBlobAtomic( OperationPurpose purpose, String blobName, long blobSize, - CheckedBiFunction provider, + BlobMultiPartInputStreamProvider provider, boolean failIfAlreadyExists ) throws IOException { blobStore.writeBlobAtomic(purpose, buildKey(blobName), blobSize, provider, failIfAlreadyExists); diff --git a/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java b/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java index 818cc4c0cd560..f027492393aff 100644 --- a/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java +++ b/modules/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java @@ -50,7 +50,6 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.util.Throwables; import org.elasticsearch.cluster.metadata.RepositoryMetadata; -import org.elasticsearch.common.CheckedBiFunction; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.blobstore.BlobContainer; import org.elasticsearch.common.blobstore.BlobPath; @@ -477,7 +476,7 @@ void writeBlobAtomic( final OperationPurpose purpose, final String blobName, final long blobSize, - final CheckedBiFunction provider, + final BlobContainer.BlobMultiPartInputStreamProvider provider, final boolean failIfAlreadyExists ) throws IOException { try { @@ -559,7 +558,7 @@ private static Mono stageBlock( BlockBlobAsyncClient asyncClient, String blobName, MultiPart multiPart, - CheckedBiFunction provider + BlobContainer.BlobMultiPartInputStreamProvider provider ) { logger.debug( "{}: staging part [{}] of size [{}] from offset [{}]", diff --git a/server/src/main/java/org/elasticsearch/common/blobstore/BlobContainer.java b/server/src/main/java/org/elasticsearch/common/blobstore/BlobContainer.java index b33fa9cd6b117..c5e9089f93900 100644 --- a/server/src/main/java/org/elasticsearch/common/blobstore/BlobContainer.java +++ b/server/src/main/java/org/elasticsearch/common/blobstore/BlobContainer.java @@ -10,7 +10,6 @@ package org.elasticsearch.common.blobstore; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.common.CheckedBiFunction; import org.elasticsearch.common.blobstore.support.BlobMetadata; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -153,11 +152,42 @@ default boolean supportsConcurrentMultipartUploads() { return false; } + /** + * Provides an {@link InputStream} to read a part of the blob content. + */ + interface BlobMultiPartInputStreamProvider { + /** + * Provides an {@link InputStream} to read a part of the blob content. + * + * @param offset the offset in the blob content to start reading bytes from + * @param length the number of bytes to read + * @return an {@link InputStream} to read a part of the blob content. + * @throws IOException if something goes wrong opening the input stream + */ + InputStream apply(long offset, long length) throws IOException; + } + + /** + * Reads the blob's content by calling an input stream provider multiple times, in order to split the blob's content into multiple + * parts that can be written to the container concurrently before being assembled into the final blob, using an atomic write operation + * if the implementation supports it. The number and the size of the parts depends of the implementation. + * + * Note: the method {link {@link #supportsConcurrentMultipartUploads()}} must be checked before calling this method. + * + * @param purpose The purpose of the operation + * @param blobName The name of the blob to write the contents of the input stream to. + * @param provider The input stream provider that is used to read the blob content + * @param blobSize The size of the blob to be written, in bytes. Must be the amount of bytes in the input stream. It is + * implementation dependent whether this value is used in writing the blob to the repository. + * @param failIfAlreadyExists whether to throw a FileAlreadyExistsException if the given blob already exists + * @throws FileAlreadyExistsException if failIfAlreadyExists is true and a blob by the same name already exists + * @throws IOException if the input stream could not be read, or the target blob could not be written to. + */ default void writeBlobAtomic( OperationPurpose purpose, String blobName, long blobSize, - CheckedBiFunction provider, + BlobMultiPartInputStreamProvider provider, boolean failIfAlreadyExists ) throws IOException { throw new UnsupportedOperationException(); diff --git a/server/src/main/java/org/elasticsearch/common/blobstore/fs/FsBlobContainer.java b/server/src/main/java/org/elasticsearch/common/blobstore/fs/FsBlobContainer.java index b6b33f96f3c5c..494006eb1a719 100644 --- a/server/src/main/java/org/elasticsearch/common/blobstore/fs/FsBlobContainer.java +++ b/server/src/main/java/org/elasticsearch/common/blobstore/fs/FsBlobContainer.java @@ -89,6 +89,10 @@ public FsBlobContainer(FsBlobStore blobStore, BlobPath blobPath, Path path) { this.path = path; } + public Path getPath() { + return path; + } + @Override public Map listBlobs(OperationPurpose purpose) throws IOException { return listBlobsByPrefix(purpose, null); diff --git a/server/src/main/java/org/elasticsearch/common/blobstore/support/FilterBlobContainer.java b/server/src/main/java/org/elasticsearch/common/blobstore/support/FilterBlobContainer.java index 4de563a9e292e..f2736c6be86ae 100644 --- a/server/src/main/java/org/elasticsearch/common/blobstore/support/FilterBlobContainer.java +++ b/server/src/main/java/org/elasticsearch/common/blobstore/support/FilterBlobContainer.java @@ -88,6 +88,22 @@ public void writeMetadataBlob( delegate.writeMetadataBlob(purpose, blobName, failIfAlreadyExists, atomic, writer); } + @Override + public boolean supportsConcurrentMultipartUploads() { + return delegate.supportsConcurrentMultipartUploads(); + } + + @Override + public void writeBlobAtomic( + OperationPurpose purpose, + String blobName, + long blobSize, + BlobMultiPartInputStreamProvider provider, + boolean failIfAlreadyExists + ) throws IOException { + delegate.writeBlobAtomic(purpose, blobName, blobSize, provider, failIfAlreadyExists); + } + @Override public void writeBlobAtomic( OperationPurpose purpose, From 2dca633486dd546b6e32999f09a786343c6104b4 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Fri, 6 Jun 2025 17:28:38 +0200 Subject: [PATCH 004/102] Unmute FollowingEngineTests#testProcessOnceOnPrimary() test (#129054) The reason the test fails is that operations contained _seq_no field with different doc value types (with no skippers and with skippers) and this isn't allowed, since field types need to be consistent in a Lucene index. The initial operations were generated not knowing about the fact the index mode was set to logsdb or time_series. Causing the operations to not have doc value skippers. However when replaying the operations via following engine, the operations did have doc value skippers. The fix is to set `index.seq_no.index_options` to `points_and_doc_values`, so that the initial operations are indexed without doc value skippers. This test doesn't gain anything from storing seqno with doc value skippers, so there is no loss of testing coverage. Closes #128541 --- muted-tests.yml | 3 --- .../xpack/ccr/index/engine/FollowingEngineTests.java | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/muted-tests.yml b/muted-tests.yml index 758bfa607e9ef..2d8240e87a6c1 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -495,9 +495,6 @@ tests: - class: org.elasticsearch.xpack.inference.InferenceGetServicesIT method: testGetServicesWithCompletionTaskType issue: https://github.com/elastic/elasticsearch/issues/128952 -- class: org.elasticsearch.xpack.ccr.index.engine.FollowingEngineTests - method: testProcessOnceOnPrimary - issue: https://github.com/elastic/elasticsearch/issues/128541 - class: org.elasticsearch.packaging.test.DockerTests method: test073RunEsAsDifferentUserAndGroupWithoutBindMounting issue: https://github.com/elastic/elasticsearch/issues/128996 diff --git a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java index b6ced318c1699..64d94f611f7b2 100644 --- a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java +++ b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java @@ -771,9 +771,11 @@ public void testProcessOnceOnPrimary() throws Exception { break; case TIME_SERIES: settingsBuilder.put("index.mode", "time_series").put("index.routing_path", "foo"); + settingsBuilder.put("index.seq_no.index_options", "points_and_doc_values"); break; case LOGSDB: settingsBuilder.put("index.mode", IndexMode.LOGSDB.getName()); + settingsBuilder.put("index.seq_no.index_options", "points_and_doc_values"); break; case LOOKUP: settingsBuilder.put("index.mode", IndexMode.LOOKUP.getName()); From 4c0e3c9ca0c3ea8c5f0de034fae18353c9be3bb3 Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Fri, 6 Jun 2025 17:35:44 +0200 Subject: [PATCH 005/102] [Build] Add support for publishing to maven central (#128659) This ensures we package an aggregation zip with all artifacts we want to publish to maven central as part of a release. Running zipAggregation will produce a zip file in the build/nmcp/zip folder. The content of this zip is meant to match the maven artifacts we have currently declared as dra maven artifacts. --- build-conventions/build.gradle | 123 ++--- .../internal/conventions/PublishPlugin.java | 9 + .../internal/BuildPluginFuncTest.groovy | 3 - .../internal/PublishPluginFuncTest.groovy | 434 +++++++++++++----- .../gradle/internal/BuildPlugin.java | 3 +- .../fixtures/AbstractGradleFuncTest.groovy | 62 +++ build.gradle | 24 +- client/test/build.gradle | 4 - gradle/build.versions.toml | 3 + gradle/verification-metadata.xml | 50 ++ libs/entitlement/bridge/build.gradle | 1 + .../tools/public-callers-finder/build.gradle | 1 - .../securitymanager-scanner/build.gradle | 1 - test/x-content/build.gradle | 1 - test/yaml-rest-runner/build.gradle | 1 + .../core/template-resources/build.gradle | 2 +- x-pack/plugin/identity-provider/build.gradle | 1 - x-pack/plugin/kql/build.gradle | 1 - 18 files changed, 532 insertions(+), 192 deletions(-) diff --git a/build-conventions/build.gradle b/build-conventions/build.gradle index b0eda5a34065a..9416c3028e8ee 100644 --- a/build-conventions/build.gradle +++ b/build-conventions/build.gradle @@ -17,88 +17,89 @@ buildscript { } plugins { - id 'java-gradle-plugin' - id 'java-test-fixtures' - id 'eclipse' + id 'java-gradle-plugin' + id 'java-test-fixtures' + id 'eclipse' } group = "org.elasticsearch" // This project contains Checkstyle rule implementations used by IDEs which use a Java 11 runtime java { - targetCompatibility = 11 - sourceCompatibility = 11 + targetCompatibility = 17 + sourceCompatibility = 17 } gradlePlugin { - // We already configure publication and we don't need or want the one that comes - // with the java-gradle-plugin - automatedPublishing = false - plugins { - internalLicenseheaders { - id = 'elasticsearch.internal-licenseheaders' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.precommit.LicenseHeadersPrecommitPlugin' - } - eclipse { - id = 'elasticsearch.eclipse' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.EclipseConventionPlugin' - } - publish { - id = 'elasticsearch.publish' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.PublishPlugin' - } - licensing { - id = 'elasticsearch.licensing' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.LicensingPlugin' - } - buildTools { - id = 'elasticsearch.build-tools' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.BuildToolsConventionsPlugin' - } - versions { - id = 'elasticsearch.versions' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.VersionPropertiesPlugin' - } - formatting { - id = 'elasticsearch.formatting' - implementationClass = 'org.elasticsearch.gradle.internal.conventions.precommit.FormattingPrecommitPlugin' - } + // We already configure publication and we don't need or want the one that comes + // with the java-gradle-plugin + automatedPublishing = false + plugins { + internalLicenseheaders { + id = 'elasticsearch.internal-licenseheaders' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.precommit.LicenseHeadersPrecommitPlugin' } + eclipse { + id = 'elasticsearch.eclipse' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.EclipseConventionPlugin' + } + publish { + id = 'elasticsearch.publish' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.PublishPlugin' + } + licensing { + id = 'elasticsearch.licensing' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.LicensingPlugin' + } + buildTools { + id = 'elasticsearch.build-tools' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.BuildToolsConventionsPlugin' + } + versions { + id = 'elasticsearch.versions' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.VersionPropertiesPlugin' + } + formatting { + id = 'elasticsearch.formatting' + implementationClass = 'org.elasticsearch.gradle.internal.conventions.precommit.FormattingPrecommitPlugin' + } + } } repositories { - mavenCentral() - gradlePluginPortal() + mavenCentral() + gradlePluginPortal() } dependencies { - api buildLibs.maven.model - api buildLibs.shadow.plugin - api buildLibs.apache.rat - compileOnly buildLibs.checkstyle - constraints { - api("org.eclipse.platform:org.eclipse.osgi:3.18.300") { - because("Use the same version as we do in spotless gradle plugin at runtime") - } - } - api(buildLibs.spotless.plugin) { - exclude module: "groovy-xml" + api buildLibs.maven.model + api buildLibs.shadow.plugin + api buildLibs.apache.rat + api buildLibs.nmcp + compileOnly buildLibs.checkstyle + constraints { + api("org.eclipse.platform:org.eclipse.osgi:3.18.300") { + because("Use the same version as we do in spotless gradle plugin at runtime") } + } + api(buildLibs.spotless.plugin) { + exclude module: "groovy-xml" + } } project.getPlugins().withType(JavaBasePlugin.class) { - java.getModularity().getInferModulePath().set(false); - eclipse.getClasspath().getFile().whenMerged { classpath -> - /* - * give each source folder a unique corresponding output folder - * outside of the usual `build` folder. We can't put the build - * in the usual build folder because eclipse becomes *very* sad - * if we delete it. Which `gradlew clean` does all the time. - */ - classpath.getEntries().findAll{ s -> s instanceof SourceFolder }.eachWithIndex { s, i -> - s.setOutput("out/eclipse" + i) - } + java.getModularity().getInferModulePath().set(false); + eclipse.getClasspath().getFile().whenMerged { classpath -> + /* + * give each source folder a unique corresponding output folder + * outside of the usual `build` folder. We can't put the build + * in the usual build folder because eclipse becomes *very* sad + * if we delete it. Which `gradlew clean` does all the time. + */ + classpath.getEntries().findAll { s -> s instanceof SourceFolder }.eachWithIndex { s, i -> + s.setOutput("out/eclipse" + i) } + } } tasks.withType(JavaCompile).configureEach { diff --git a/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/PublishPlugin.java b/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/PublishPlugin.java index 22b0ab1918024..d3f03b9534be3 100644 --- a/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/PublishPlugin.java +++ b/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/PublishPlugin.java @@ -14,6 +14,8 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowExtension; import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin; +import nmcp.NmcpPlugin; + import org.elasticsearch.gradle.internal.conventions.info.GitInfo; import org.elasticsearch.gradle.internal.conventions.precommit.PomValidationPrecommitPlugin; import org.elasticsearch.gradle.internal.conventions.util.Util; @@ -27,6 +29,7 @@ import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.plugins.JavaLibraryPlugin; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; @@ -65,6 +68,7 @@ public void apply(Project project) { project.getPluginManager().apply(MavenPublishPlugin.class); project.getPluginManager().apply(PomValidationPrecommitPlugin.class); project.getPluginManager().apply(LicensingPlugin.class); + project.getPluginManager().apply(NmcpPlugin.class); configureJavadocJar(project); configureSourcesJar(project); configurePomGeneration(project); @@ -82,6 +86,11 @@ private void configurePublications(Project project) { publication.from(project.getComponents().getByName("java")); } }); + project.getPlugins().withType(JavaPlugin.class, plugin -> { + var javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + javaPluginExtension.withJavadocJar(); + javaPluginExtension.withSourcesJar(); + }); @SuppressWarnings("unchecked") var projectLicenses = (MapProperty>) project.getExtensions().getExtraProperties().get("projectLicenses"); publication.getPom().withXml(xml -> { diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/BuildPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/BuildPluginFuncTest.groovy index 63bb732d8a11d..b223546623a00 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/BuildPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/BuildPluginFuncTest.groovy @@ -123,8 +123,6 @@ class BuildPluginFuncTest extends AbstractGradleFuncTest { then: result.task(":assemble").outcome == TaskOutcome.SUCCESS file("build/distributions/hello-world.jar").exists() - file("build/distributions/hello-world-javadoc.jar").exists() - file("build/distributions/hello-world-sources.jar").exists() assertValidJar(file("build/distributions/hello-world.jar")) } @@ -162,7 +160,6 @@ class BuildPluginFuncTest extends AbstractGradleFuncTest { result.task(":forbiddenPatterns").outcome == TaskOutcome.SUCCESS result.task(":validateModule").outcome == TaskOutcome.SUCCESS result.task(":splitPackagesAudit").outcome == TaskOutcome.SUCCESS - result.task(":validateElasticPom").outcome == TaskOutcome.SUCCESS // disabled but check for being on the task graph result.task(":forbiddenApisMain").outcome == TaskOutcome.SKIPPED result.task(":checkstyleMain").outcome == TaskOutcome.SKIPPED diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/PublishPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/PublishPluginFuncTest.groovy index cc551057cd600..4479ac8da3d22 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/PublishPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/PublishPluginFuncTest.groovy @@ -23,30 +23,233 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { configurationCacheCompatible = false } - def "artifacts and tweaked pom is published"() { + def "project with plugin applied is considered for maven central publication"() { given: - buildFile << """ + // required for JarHell to work + subProject(":libs:some-public-lib") << """ + plugins { + id 'elasticsearch.java' + id 'elasticsearch.publish' + } + + group = 'org.acme' + version = '1.0' + """ + + subProject(":libs:some-other-lib") << """ plugins { id 'elasticsearch.java' id 'elasticsearch.publish' } + group = 'org.acme.xpack' + version = '1.0' + """ + + subProject(":libs:some-private-lib") << """ + plugins { + id 'elasticsearch.java' + } + + group = 'org.acme.xpack' + version = '1.0' + """ + + buildFile << """ + plugins { + id 'com.gradleup.nmcp.aggregation' + } + version = "1.0" group = 'org.acme' description = "custom project description" + nmcpAggregation { + centralPortal { + username = 'acme' + password = 'acmepassword' + // publish manually from the portal + publishingType = "USER_MANAGED" + } + // this breaks project isolation but this is broken in elasticsearch build atm anyhow. + publishAllProjectsProbablyBreakingProjectIsolation() + } """ when: - def result = gradleRunner('assemble').build() + def result = gradleRunner(':zipAggregation').build() then: - result.task(":generatePom").outcome == TaskOutcome.SUCCESS - file("build/distributions/hello-world-1.0.jar").exists() - file("build/distributions/hello-world-1.0-javadoc.jar").exists() - file("build/distributions/hello-world-1.0-sources.jar").exists() - file("build/distributions/hello-world-1.0.pom").exists() - assertXmlEquals( - file("build/distributions/hello-world-1.0.pom").text, """ + result.task(":zipAggregation").outcome == TaskOutcome.SUCCESS + file("build/nmcp/zip/aggregation.zip").exists() + + + def zip = zip("build/nmcp/zip/aggregation.zip") + zip.files().findAll { it.isDirectory() == false }.collect { it.name }.sort() == [ + "org/acme/some-public-lib/1.0/some-public-lib-1.0-javadoc.jar", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-javadoc.jar.md5", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-javadoc.jar.sha1", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-javadoc.jar.sha256", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-javadoc.jar.sha512", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-sources.jar", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-sources.jar.md5", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-sources.jar.sha1", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-sources.jar.sha256", + "org/acme/some-public-lib/1.0/some-public-lib-1.0-sources.jar.sha512", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.jar", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.jar.md5", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.jar.sha1", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.jar.sha256", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.jar.sha512", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.module", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.module.md5", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.module.sha1", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.module.sha256", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.module.sha512", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.pom", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.pom.md5", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.pom.sha1", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.pom.sha256", + "org/acme/some-public-lib/1.0/some-public-lib-1.0.pom.sha512", + + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-javadoc.jar", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-javadoc.jar.md5", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-javadoc.jar.sha1", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-javadoc.jar.sha256", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-javadoc.jar.sha512", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-sources.jar", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-sources.jar.md5", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-sources.jar.sha1", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-sources.jar.sha256", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0-sources.jar.sha512", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.jar", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.jar.md5", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.jar.sha1", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.jar.sha256", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.jar.sha512", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.module", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.module.md5", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.module.sha1", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.module.sha256", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.module.sha512", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.pom", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.pom.md5", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.pom.sha1", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.pom.sha256", + "org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.pom.sha512" + ] + + assertXmlEquals(zip.file("org/acme/some-public-lib/1.0/some-public-lib-1.0.pom").read(),""" + + + + + + + 4.0.0 + org.acme + some-public-lib + 1.0 + some-public-lib + + unknown + + unknown + + 2009 + + + Elastic License 2.0 + https://raw.githubusercontent.com/elastic/elasticsearch/v1.0/licenses/ELASTIC-LICENSE-2.0.txt + repo + + + GNU Affero General Public License Version 3 + https://raw.githubusercontent.com/elastic/elasticsearch/v1.0/licenses/AGPL-3.0+SSPL-1.0+ELASTIC-LICENSE-2.0.txt + repo + + + Server Side Public License, v 1 + https://www.mongodb.com/licensing/server-side-public-license + repo + + + + + Elastic + https://www.elastic.co + + + +""") + assertXmlEquals(zip.file("org/acme/xpack/some-other-lib/1.0/some-other-lib-1.0.pom").read(),""" + + + + + + + 4.0.0 + org.acme.xpack + some-other-lib + 1.0 + some-other-lib + + unknown + + unknown + + 2009 + + + Elastic License 2.0 + https://raw.githubusercontent.com/elastic/elasticsearch/v1.0/licenses/ELASTIC-LICENSE-2.0.txt + repo + + + GNU Affero General Public License Version 3 + https://raw.githubusercontent.com/elastic/elasticsearch/v1.0/licenses/AGPL-3.0+SSPL-1.0+ELASTIC-LICENSE-2.0.txt + repo + + + Server Side Public License, v 1 + https://www.mongodb.com/licensing/server-side-public-license + repo + + + + + Elastic + https://www.elastic.co + + + +""") +} + +def "artifacts and tweaked pom is published"() { + given: + buildFile << """ + plugins { + id 'elasticsearch.java' + id 'elasticsearch.publish' + } + + version = "1.0" + group = 'org.acme' + description = "custom project description" + """ + + when: + def result = gradleRunner('assemble').build() + + then: + result.task(":generatePom").outcome == TaskOutcome.SUCCESS + file("build/distributions/hello-world-1.0.jar").exists() + file("build/distributions/hello-world-1.0-javadoc.jar").exists() + file("build/distributions/hello-world-1.0-sources.jar").exists() + file("build/distributions/hello-world-1.0.pom").exists() + assertXmlEquals( + file("build/distributions/hello-world-1.0.pom").text, """ @@ -88,12 +291,12 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { """ - ) - } + ) +} - def "hides runtime dependencies and handles shadow dependencies"() { - given: - buildFile << """ +def "hides runtime dependencies and handles shadow dependencies"() { + given: + buildFile << """ plugins { id 'elasticsearch.java' id 'elasticsearch.publish' @@ -121,18 +324,18 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { description = 'shadowed project' """ - when: - def result = gradleRunner('assemble', '--stacktrace').build() - - then: - result.task(":generatePom").outcome == TaskOutcome.SUCCESS - file("build/distributions/hello-world-1.0-original.jar").exists() - file("build/distributions/hello-world-1.0.jar").exists() - file("build/distributions/hello-world-1.0-javadoc.jar").exists() - file("build/distributions/hello-world-1.0-sources.jar").exists() - file("build/distributions/hello-world-1.0.pom").exists() - assertXmlEquals( - file("build/distributions/hello-world-1.0.pom").text, """ + when: + def result = gradleRunner('assemble', '--stacktrace').build() + + then: + result.task(":generatePom").outcome == TaskOutcome.SUCCESS + file("build/distributions/hello-world-1.0-original.jar").exists() + file("build/distributions/hello-world-1.0.jar").exists() + file("build/distributions/hello-world-1.0-javadoc.jar").exists() + file("build/distributions/hello-world-1.0-sources.jar").exists() + file("build/distributions/hello-world-1.0.pom").exists() + assertXmlEquals( + file("build/distributions/hello-world-1.0.pom").text, """ 4.0.0 org.acme @@ -177,14 +380,14 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { """ - ) - } + ) +} - def "handles project shadow dependencies"() { - given: - settingsFile << "include ':someLib'" - file('someLib').mkdirs() - buildFile << """ +def "handles project shadow dependencies"() { + given: + settingsFile << "include ':someLib'" + file('someLib').mkdirs() + buildFile << """ plugins { id 'elasticsearch.java' id 'elasticsearch.publish' @@ -211,18 +414,18 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { description = 'with shadowed dependencies' """ - when: - def result = gradleRunner(':assemble', '--stacktrace').build() - - then: - result.task(":generatePom").outcome == TaskOutcome.SUCCESS - file("build/distributions/hello-world-1.0-original.jar").exists() - file("build/distributions/hello-world-1.0.jar").exists() - file("build/distributions/hello-world-1.0-javadoc.jar").exists() - file("build/distributions/hello-world-1.0-sources.jar").exists() - file("build/distributions/hello-world-1.0.pom").exists() - assertXmlEquals( - file("build/distributions/hello-world-1.0.pom").text, """ + when: + def result = gradleRunner(':assemble', '--stacktrace').build() + + then: + result.task(":generatePom").outcome == TaskOutcome.SUCCESS + file("build/distributions/hello-world-1.0-original.jar").exists() + file("build/distributions/hello-world-1.0.jar").exists() + file("build/distributions/hello-world-1.0-javadoc.jar").exists() + file("build/distributions/hello-world-1.0-sources.jar").exists() + file("build/distributions/hello-world-1.0.pom").exists() + assertXmlEquals( + file("build/distributions/hello-world-1.0.pom").text, """ 4.0.0 org.acme @@ -267,16 +470,16 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { """ - ) - } + ) +} - def "generates artifacts for shadowed elasticsearch plugin"() { - given: - // we use the esplugin plugin in this test that is not configuration cache compatible yet - configurationCacheCompatible = false - file('license.txt') << "License file" - file('notice.txt') << "Notice file" - buildFile << """ +def "generates artifacts for shadowed elasticsearch plugin"() { + given: + // we use the esplugin plugin in this test that is not configuration cache compatible yet + configurationCacheCompatible = false + file('license.txt') << "License file" + file('notice.txt') << "Notice file" + buildFile << """ plugins { id 'elasticsearch.internal-es-plugin' id 'elasticsearch.publish' @@ -305,18 +508,18 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { group = 'org.acme' """ - when: - def result = gradleRunner('assemble', '--stacktrace', '-x', 'generateClusterFeaturesMetadata').build() - - then: - result.task(":generatePom").outcome == TaskOutcome.SUCCESS - file("build/distributions/hello-world-plugin-1.0-original.jar").exists() - file("build/distributions/hello-world-plugin-1.0.jar").exists() - file("build/distributions/hello-world-plugin-1.0-javadoc.jar").exists() - file("build/distributions/hello-world-plugin-1.0-sources.jar").exists() - file("build/distributions/hello-world-plugin-1.0.pom").exists() - assertXmlEquals( - file("build/distributions/hello-world-plugin-1.0.pom").text, """ + when: + def result = gradleRunner('assemble', '--stacktrace', '-x', 'generateClusterFeaturesMetadata').build() + + then: + result.task(":generatePom").outcome == TaskOutcome.SUCCESS + file("build/distributions/hello-world-plugin-1.0-original.jar").exists() + file("build/distributions/hello-world-plugin-1.0.jar").exists() + file("build/distributions/hello-world-plugin-1.0-javadoc.jar").exists() + file("build/distributions/hello-world-plugin-1.0-sources.jar").exists() + file("build/distributions/hello-world-plugin-1.0.pom").exists() + assertXmlEquals( + file("build/distributions/hello-world-plugin-1.0.pom").text, """ @@ -358,16 +561,16 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { """ - ) - } + ) +} - def "generates pom for elasticsearch plugin"() { - given: - // we use the esplugin plugin in this test that is not configuration cache compatible yet - configurationCacheCompatible = false - file('license.txt') << "License file" - file('notice.txt') << "Notice file" - buildFile << """ +def "generates pom for elasticsearch plugin"() { + given: + // we use the esplugin plugin in this test that is not configuration cache compatible yet + configurationCacheCompatible = false + file('license.txt') << "License file" + file('notice.txt') << "Notice file" + buildFile << """ plugins { id 'elasticsearch.internal-es-plugin' id 'elasticsearch.publish' @@ -387,14 +590,14 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { group = 'org.acme' """ - when: - def result = gradleRunner('generatePom').build() + when: + def result = gradleRunner('generatePom').build() - then: - result.task(":generatePom").outcome == TaskOutcome.SUCCESS - file("build/distributions/hello-world-plugin-2.0.pom").exists() - assertXmlEquals( - file("build/distributions/hello-world-plugin-2.0.pom").text, """ + then: + result.task(":generatePom").outcome == TaskOutcome.SUCCESS + file("build/distributions/hello-world-plugin-2.0.pom").exists() + assertXmlEquals( + file("build/distributions/hello-world-plugin-2.0.pom").text, """ @@ -436,14 +639,14 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { """ - ) - } + ) +} - def "generated pom can be validated"() { - given: - // scm info only added for internal builds - internalBuild() - buildFile << """ +def "generated pom can be validated"() { + given: + // scm info only added for internal builds + internalBuild() + buildFile << """ buildParams.setGitOrigin(project.providers.provider(() -> "https://some-repo.com/repo.git")) apply plugin:'elasticsearch.java' apply plugin:'elasticsearch.publish' @@ -455,14 +658,14 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { ext.projectLicenses.set(['The Apache Software License, Version 2.0': project.providers.provider(() -> 'http://www.apache.org/licenses/LICENSE-2.0')]) """ - when: - def result = gradleRunner('generatePom', 'validateElasticPom').build() + when: + def result = gradleRunner('generatePom', 'validateElasticPom').build() - then: - result.task(":generatePom").outcome == TaskOutcome.SUCCESS - file("build/distributions/hello-world-1.0.pom").exists() - assertXmlEquals( - file("build/distributions/hello-world-1.0.pom").text, """ + then: + result.task(":generatePom").outcome == TaskOutcome.SUCCESS + file("build/distributions/hello-world-1.0.pom").exists() + assertXmlEquals( + file("build/distributions/hello-world-1.0.pom").text, """ @@ -494,30 +697,31 @@ class PublishPluginFuncTest extends AbstractGradleFuncTest { """ - ) - } + ) +} - private boolean assertXmlEquals(String toTest, String expected) { - def diff = DiffBuilder.compare(Input.fromString(expected)) - .ignoreWhitespace() - .ignoreComments() - .normalizeWhitespace() - .withTest(Input.fromString(toTest)) - .build() - diff.differences.each { difference -> - println difference - } - if (diff.differences.size() > 0) { - println """ given: +private boolean assertXmlEquals(String toTest, String expected) { + def diff = DiffBuilder.compare(Input.fromString(expected)) + .ignoreWhitespace() + .ignoreComments() + .normalizeWhitespace() + .withTest(Input.fromString(toTest)) + .build() + diff.differences.each { difference -> + println difference + } + if (diff.differences.size() > 0) { + println """ given: $toTest """ - println """ expected: + println """ expected: $expected """ - } - assert diff.hasDifferences() == false - true } + assert diff.hasDifferences() == false + true +} + } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BuildPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BuildPlugin.java index fb8a9858e24d5..3a4ed6dae0fa1 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BuildPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BuildPlugin.java @@ -9,6 +9,7 @@ package org.elasticsearch.gradle.internal; +import org.elasticsearch.gradle.internal.conventions.LicensingPlugin; import org.elasticsearch.gradle.internal.info.GlobalBuildInfoPlugin; import org.elasticsearch.gradle.internal.precommit.InternalPrecommitTasks; import org.elasticsearch.gradle.internal.snyk.SnykDependencyMonitoringGradlePlugin; @@ -59,9 +60,9 @@ public void apply(final Project project) { } project.getPluginManager().apply("elasticsearch.java"); - project.getPluginManager().apply("elasticsearch.publish"); project.getPluginManager().apply(ElasticsearchJavadocPlugin.class); project.getPluginManager().apply(DependenciesInfoPlugin.class); + project.getPluginManager().apply(LicensingPlugin.class); project.getPluginManager().apply(SnykDependencyMonitoringGradlePlugin.class); project.getPluginManager().apply(ClusterFeaturesMetadataPlugin.class); InternalPrecommitTasks.create(project, true); diff --git a/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy b/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy index 72d8134869037..d52b7d321c729 100644 --- a/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy +++ b/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy @@ -10,6 +10,7 @@ package org.elasticsearch.gradle.fixtures import org.apache.commons.io.FileUtils +import org.apache.commons.io.IOUtils import org.elasticsearch.gradle.internal.test.BuildConfigurationAwareGradleRunner import org.elasticsearch.gradle.internal.test.InternalAwareGradleRunner import org.elasticsearch.gradle.internal.test.NormalizeOutputGradleRunner @@ -23,11 +24,14 @@ import spock.lang.Specification import spock.lang.TempDir import java.lang.management.ManagementFactory +import java.nio.charset.StandardCharsets import java.nio.file.Files import java.io.File import java.nio.file.Path import java.util.jar.JarEntry import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipFile import static org.elasticsearch.gradle.internal.test.TestUtils.normalizeString @@ -234,6 +238,64 @@ checkstyle = "com.puppycrawl.tools:checkstyle:10.3" (it as TestResultExtension.ErrorListener).errorInfo != null } } + ZipAssertion zip(String relativePath) { + File archiveFile = file(relativePath); + try (ZipFile zipFile = new ZipFile(archiveFile)) { + Map files = zipFile.entries().collectEntries { ZipEntry entry -> + [(entry.name): new ZipAssertionFile(archiveFile, entry)] + } + return new ZipAssertion(files); + } + } + + static class ZipAssertion { + private Map files = new HashMap<>() + + ZipAssertion(Map files) { + this.files = files; + } + + ZipAssertionFile file(String path) { + return this.files.get(path) + } + + Collection files() { + return files.values() + } + } + + static class ZipAssertionFile { + + private ZipEntry entry; + private File zipFile; + + ZipAssertionFile(File zipFile, ZipEntry entry) { + this.entry = entry + this.zipFile = zipFile + } + + boolean exists() { + entry == null + } + + String getName() { + return entry.name + } + + boolean isDirectory() { + return entry.isDirectory() + } + + String read() { + try(ZipFile zipFile1 = new ZipFile(zipFile)) { + def inputStream = zipFile1.getInputStream(entry) + return IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()) + } catch (IOException e) { + throw new RuntimeException("Failed to read entry ${entry.name} from zip file ${zipFile.name}", e) + } + } + } + static class ProjectConfigurer { private File projectDir diff --git a/build.gradle b/build.gradle index 7605eefc49b64..ed344b04a58f6 100644 --- a/build.gradle +++ b/build.gradle @@ -48,8 +48,28 @@ plugins { id 'elasticsearch.internal-testclusters' id 'elasticsearch.run' id 'elasticsearch.run-ccs' + id 'elasticsearch.repositories' id 'elasticsearch.release-tools' id 'elasticsearch.versions' + id 'com.gradleup.nmcp.aggregation' +} + +version = VersionProperties.elasticsearch + +/** + * Here we package and aggregation zip file containing all maven artifacts we want to + * publish to maven central. + * The aggregation is done by picking all projects that have the elasticsearch.publish plugin applied, + * indicating the artifact is meant for beeing published to maven central. + * */ +nmcpAggregation { + // this breaks project isolation but this is broken in elasticsearch build atm anyhow. + publishAllProjectsProbablyBreakingProjectIsolation() +} + +tasks.named('zipAggregation').configure { + dependsOn gradle.includedBuild('build-tools').task(':zipElasticPublication') + from(zipTree(gradle.includedBuild('build-tools').task(':zipElasticPublication').resolveTask().archiveFile.get())) } /** @@ -295,7 +315,7 @@ allprojects { tasks.register('resolveAllDependencies', ResolveAllDependencies) { def ignoredPrefixes = [DistributionDownloadPlugin.ES_DISTRO_CONFIG_PREFIX, "jdbcDriver"] configs = project.configurations.matching { config -> ignoredPrefixes.any { config.name.startsWith(it) } == false } - if(project.path == ':') { + if (project.path == ':') { resolveJavaToolChain = true // ensure we have best possible caching of bwc builds @@ -320,7 +340,7 @@ allprojects { } ext.withReleaseBuild = { Closure config -> - if(buildParams.snapshotBuild == false) { + if (buildParams.snapshotBuild == false) { config.call() } } diff --git a/client/test/build.gradle b/client/test/build.gradle index 27b1577ce3098..3149737eaeca1 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -17,10 +17,6 @@ java { group = "${group}.client.test" -// rest client sniffer is licenses under Apache 2.0 -projectLicenses.set(['The Apache Software License, Version 2.0': providers.provider(() -> 'http://www.apache.org/licenses/LICENSE-2.0')]) -licenseFile.set(layout.getSettingsDirectory().file('licenses/APACHE-LICENSE-2.0.txt').asFile) - dependencies { api "org.apache.httpcomponents:httpcore:${versions.httpcore}" api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" diff --git a/gradle/build.versions.toml b/gradle/build.versions.toml index ae5dac9a29343..bf0e6873f9073 100644 --- a/gradle/build.versions.toml +++ b/gradle/build.versions.toml @@ -3,6 +3,7 @@ asm = "9.7.1" jackson = "2.15.0" junit5 = "5.12.1" spock = "2.1-groovy-3.0" +nmcp = "0.1.5" [libraries] ant = "org.apache.ant:ant:1.10.12" @@ -36,6 +37,7 @@ junit5-platform-launcher = "org.junit.platform:junit-platform-launcher:1.8.1" junit5-vintage = { group = "org.junit.vintage", name="junit-vintage-engine", version.ref="junit5" } maven-model = "org.apache.maven:maven-model:3.6.2" mockito-core = "org.mockito:mockito-core:1.9.5" +nmcp = { group = "com.gradleup.nmcp", name = "nmcp", version.ref="nmcp" } nebula-info = "com.netflix.nebula:gradle-info-plugin:11.3.3" reflections = "org.reflections:reflections:0.9.12" shadow-plugin = "com.gradleup.shadow:shadow-gradle-plugin:8.3.5" @@ -49,3 +51,4 @@ xmlunit-core = "org.xmlunit:xmlunit-core:2.8.2" [plugins] ospackage = { id = "com.netflix.nebula.ospackage-base", version = "11.11.2" } +nmcp-aggregation = { id = "com.gradleup.nmcp.aggregation", version.ref="nmcp" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 566a640e9bd55..cf520c208cfd8 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -873,6 +873,16 @@ + + + + + + + + + + @@ -1068,6 +1078,11 @@ + + + + + @@ -1088,6 +1103,11 @@ + + + + + @@ -3991,21 +4011,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/entitlement/bridge/build.gradle b/libs/entitlement/bridge/build.gradle index 5dec95b4b9bb4..73a9226a9e0a8 100644 --- a/libs/entitlement/bridge/build.gradle +++ b/libs/entitlement/bridge/build.gradle @@ -11,6 +11,7 @@ import org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.mrjar' +apply plugin: 'elasticsearch.publish' tasks.named('jar').configure { // guarding for intellij diff --git a/libs/entitlement/tools/public-callers-finder/build.gradle b/libs/entitlement/tools/public-callers-finder/build.gradle index 083b1a43b9794..664e6e763c4dd 100644 --- a/libs/entitlement/tools/public-callers-finder/build.gradle +++ b/libs/entitlement/tools/public-callers-finder/build.gradle @@ -3,7 +3,6 @@ plugins { } apply plugin: 'elasticsearch.build' -apply plugin: 'elasticsearch.publish' tasks.named("dependencyLicenses").configure { mapping from: /asm-.*/, to: 'asm' diff --git a/libs/entitlement/tools/securitymanager-scanner/build.gradle b/libs/entitlement/tools/securitymanager-scanner/build.gradle index ebb671e5487ef..93a4f74360a6e 100644 --- a/libs/entitlement/tools/securitymanager-scanner/build.gradle +++ b/libs/entitlement/tools/securitymanager-scanner/build.gradle @@ -3,7 +3,6 @@ plugins { } apply plugin: 'elasticsearch.build' -apply plugin: 'elasticsearch.publish' tasks.named("dependencyLicenses").configure { mapping from: /asm-.*/, to: 'asm' diff --git a/test/x-content/build.gradle b/test/x-content/build.gradle index 281148a0fe819..e34a0a6293dc3 100644 --- a/test/x-content/build.gradle +++ b/test/x-content/build.gradle @@ -8,7 +8,6 @@ */ apply plugin: 'elasticsearch.build' -apply plugin: 'elasticsearch.publish' dependencies { api project(":test:framework") diff --git a/test/yaml-rest-runner/build.gradle b/test/yaml-rest-runner/build.gradle index 1ae1315ac9ef7..1568e9de8d92b 100644 --- a/test/yaml-rest-runner/build.gradle +++ b/test/yaml-rest-runner/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'elasticsearch.build' +apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.internal-yaml-rest-test' apply plugin: 'elasticsearch.yaml-rest-compat-test' diff --git a/x-pack/plugin/core/template-resources/build.gradle b/x-pack/plugin/core/template-resources/build.gradle index 83c0a74facc4f..31a2522fbd9fa 100644 --- a/x-pack/plugin/core/template-resources/build.gradle +++ b/x-pack/plugin/core/template-resources/build.gradle @@ -1,5 +1,5 @@ - apply plugin: 'elasticsearch.build' +apply plugin: 'elasticsearch.publish' base { archivesName = 'x-pack-template-resources' diff --git a/x-pack/plugin/identity-provider/build.gradle b/x-pack/plugin/identity-provider/build.gradle index 2d0b3e09db2ca..42cf78886fa54 100644 --- a/x-pack/plugin/identity-provider/build.gradle +++ b/x-pack/plugin/identity-provider/build.gradle @@ -6,7 +6,6 @@ */ apply plugin: 'elasticsearch.internal-es-plugin' -apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.internal-cluster-test' esplugin { name = 'x-pack-identity-provider' diff --git a/x-pack/plugin/kql/build.gradle b/x-pack/plugin/kql/build.gradle index d2e524fe516a2..4b52ac96f11ee 100644 --- a/x-pack/plugin/kql/build.gradle +++ b/x-pack/plugin/kql/build.gradle @@ -10,7 +10,6 @@ import static org.elasticsearch.gradle.util.PlatformUtils.normalize apply plugin: 'elasticsearch.internal-es-plugin' apply plugin: 'elasticsearch.internal-cluster-test' apply plugin: 'elasticsearch.internal-yaml-rest-test' -apply plugin: 'elasticsearch.publish' esplugin { name = 'x-pack-kql' From e2189e66e3b6882fcac5d91068686e65031fdda0 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Fri, 6 Jun 2025 11:38:46 -0400 Subject: [PATCH 006/102] ESQL: Check for errors while loading blocks (#129016) Runs a sanity check after loading a block of values. Previously we were doing a quick check if assertions were enabled. Now we do two quick checks all the time. Better - we attach information about how a block was loaded when there's a problem. Relates to #128959 --- .../index/mapper/MapperServiceTestCase.java | 2 +- .../lucene/ValuesSourceReaderOperator.java | 44 ++++++++++++++++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java index 4a1d33595eb79..71452917465a7 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/MapperServiceTestCase.java @@ -103,7 +103,7 @@ import static org.mockito.Mockito.mock; /** - * Test case that lets you easilly build {@link MapperService} based on some + * Test case that lets you easily build {@link MapperService} based on some * mapping. Useful when you don't need to spin up an entire index but do * need most of the trapping of the mapping. */ diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperator.java index 62da90a72a555..d9f56340b458b 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperator.java @@ -27,7 +27,6 @@ import org.elasticsearch.compute.operator.AbstractPageMappingOperator; import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.Operator; -import org.elasticsearch.core.Assertions; import org.elasticsearch.core.Releasable; import org.elasticsearch.core.Releasables; import org.elasticsearch.index.fieldvisitor.StoredFieldLoader; @@ -161,12 +160,6 @@ public int get(int i) { many.run(); } } - if (Assertions.ENABLED) { - for (int f = 0; f < fields.length; f++) { - assert blocks[f].elementType() == ElementType.NULL || blocks[f].elementType() == fields[f].info.type - : blocks[f].elementType() + " NOT IN (NULL, " + fields[f].info.type + ")"; - } - } success = true; for (Block b : blocks) { valuesLoaded += b.getTotalValueCount(); @@ -233,6 +226,7 @@ private void loadFromSingleLeaf(Block[] blocks, int shard, int segment, BlockLoa BlockLoader.ColumnAtATimeReader columnAtATime = field.columnAtATime(ctx); if (columnAtATime != null) { blocks[f] = (Block) columnAtATime.read(loaderBlockFactory, docs); + sanityCheckBlock(columnAtATime, docs.count(), blocks[f], f); } else { rowStrideReaders.add( new RowStrideReaderWork( @@ -282,6 +276,7 @@ private void loadFromSingleLeaf(Block[] blocks, int shard, int segment, BlockLoa } for (RowStrideReaderWork work : rowStrideReaders) { blocks[work.offset] = work.build(); + sanityCheckBlock(work.reader, docs.count(), blocks[work.offset], work.offset); } } finally { Releasables.close(rowStrideReaders); @@ -385,6 +380,7 @@ void run() throws IOException { try (Block targetBlock = fieldTypeBuilders[f].build()) { target[f] = targetBlock.filter(backwards); } + sanityCheckBlock(rowStride[f], docs.getPositionCount(), target[f], f); } } @@ -561,6 +557,40 @@ protected Status status(long processNanos, int pagesProcessed, long rowsReceived return new Status(new TreeMap<>(readersBuilt), processNanos, pagesProcessed, rowsReceived, rowsEmitted, valuesLoaded); } + /** + * Quick checks for on the loaded block to make sure it looks reasonable. + * @param loader the object that did the loading - we use it to make error messages if the block is busted + * @param expectedPositions how many positions the block should have - it's as many as the incoming {@link Page} has + * @param block the block to sanity check + * @param field offset into the {@link #fields} array for the block being loaded + */ + private void sanityCheckBlock(Object loader, int expectedPositions, Block block, int field) { + if (block.getPositionCount() != expectedPositions) { + throw new IllegalStateException( + sanityCheckBlockErrorPrefix(loader, block, field) + + " has [" + + block.getPositionCount() + + "] positions instead of [" + + expectedPositions + + "]" + ); + } + if (block.elementType() != ElementType.NULL && block.elementType() != fields[field].info.type) { + throw new IllegalStateException( + sanityCheckBlockErrorPrefix(loader, block, field) + + "'s element_type [" + + block.elementType() + + "] NOT IN (NULL, " + + fields[field].info.type + + ")" + ); + } + } + + private String sanityCheckBlockErrorPrefix(Object loader, Block block, int field) { + return fields[field].info.name + "[" + loader + "]: " + block; + } + public static class Status extends AbstractPageMappingOperator.Status { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Operator.Status.class, From aec1688878c4d6960b3aacc614d190011c43f9c4 Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:44:43 +0200 Subject: [PATCH 007/102] Make `PhaseCacheManagementTests` project-aware (#129047) The functionality in `PhaseCacheManagement` was already project-aware, but these tests were still using deprecated methods. --- .../core/ilm/PhaseCacheManagementTests.java | 62 +++++-------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/PhaseCacheManagementTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/PhaseCacheManagementTests.java index 48de17840c291..a86c59220213a 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/PhaseCacheManagementTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/PhaseCacheManagementTests.java @@ -8,12 +8,9 @@ package org.elasticsearch.xpack.core.ilm; import org.elasticsearch.client.internal.Client; -import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; -import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.ProjectMetadata; -import org.elasticsearch.cluster.project.DefaultProjectResolver; import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.TimeValue; @@ -88,13 +85,11 @@ public void testRefreshPhaseJson() throws IOException { LifecyclePolicy newPolicy = new LifecyclePolicy("my-policy", phases); LifecyclePolicyMetadata policyMetadata = new LifecyclePolicyMetadata(newPolicy, Map.of(), 2L, 2L); - ClusterState existingState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder(Metadata.EMPTY_METADATA).put(meta, false).build()) - .build(); + ProjectMetadata existingProject = ProjectMetadata.builder(randomProjectIdOrDefault()).put(meta, false).build(); - ClusterState changedState = refreshPhaseDefinition(existingState, indexName, policyMetadata); + ProjectMetadata changedProject = PhaseCacheManagement.refreshPhaseDefinition(existingProject, indexName, policyMetadata); - IndexMetadata newIdxMeta = changedState.metadata().getProject().index(indexName); + IndexMetadata newIdxMeta = changedProject.index(indexName); LifecycleExecutionState afterExState = newIdxMeta.getLifecycleExecutionState(); Map beforeState = new HashMap<>(exState.build().asMap()); beforeState.remove("phase_definition"); @@ -495,15 +490,13 @@ public void testUpdateIndicesForPolicy() throws IOException { assertTrue(isIndexPhaseDefinitionUpdatable(REGISTRY, client, meta, newPolicy, null)); - ClusterState existingState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder(Metadata.EMPTY_METADATA).put(meta, false).build()) - .build(); + ProjectMetadata existingProject = ProjectMetadata.builder(randomProjectIdOrDefault()).put(meta, false).build(); logger.info("--> update for unchanged policy"); - ClusterState updatedState = updateIndicesForPolicy(existingState, REGISTRY, client, oldPolicy, policyMetadata, null); + ProjectMetadata updatedProject = updateIndicesForPolicy(existingProject, REGISTRY, client, oldPolicy, policyMetadata, null); // No change, because the policies were identical - assertThat(updatedState, equalTo(existingState)); + assertThat(updatedProject, equalTo(existingProject)); actions = new HashMap<>(); actions.put("rollover", new RolloverAction(null, null, null, 2L, null, null, null, null, null, null)); @@ -514,10 +507,10 @@ public void testUpdateIndicesForPolicy() throws IOException { policyMetadata = new LifecyclePolicyMetadata(newPolicy, Map.of(), 2L, 2L); logger.info("--> update with changed policy, but not configured in settings"); - updatedState = updateIndicesForPolicy(existingState, REGISTRY, client, oldPolicy, policyMetadata, null); + updatedProject = updateIndicesForPolicy(existingProject, REGISTRY, client, oldPolicy, policyMetadata, null); // No change, because the index doesn't have a lifecycle.name setting for this policy - assertThat(updatedState, equalTo(existingState)); + assertThat(updatedProject, equalTo(existingProject)); meta = IndexMetadata.builder(index) .settings( @@ -528,14 +521,12 @@ public void testUpdateIndicesForPolicy() throws IOException { ) .putCustom(ILM_CUSTOM_METADATA_KEY, exState.asMap()) .build(); - existingState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder(Metadata.EMPTY_METADATA).put(meta, false).build()) - .build(); + existingProject = ProjectMetadata.builder(randomProjectIdOrDefault()).put(meta, false).build(); logger.info("--> update with changed policy and this index has the policy"); - updatedState = updateIndicesForPolicy(existingState, REGISTRY, client, oldPolicy, policyMetadata, null); + updatedProject = updateIndicesForPolicy(existingProject, REGISTRY, client, oldPolicy, policyMetadata, null); - IndexMetadata newIdxMeta = updatedState.metadata().getProject().index(index); + IndexMetadata newIdxMeta = updatedProject.index(index); LifecycleExecutionState afterExState = newIdxMeta.getLifecycleExecutionState(); Map beforeState = new HashMap<>(exState.asMap()); beforeState.remove("phase_definition"); @@ -583,37 +574,18 @@ private static IndexMetadata.Builder mkMeta() { ); } - static ClusterState updateIndicesForPolicy( - final ClusterState clusterState, + static ProjectMetadata updateIndicesForPolicy( + ProjectMetadata project, final NamedXContentRegistry xContentRegistry, final Client client, final LifecyclePolicy oldPolicy, final LifecyclePolicyMetadata newPolicy, XPackLicenseState licenseState ) { - ProjectMetadata projectMetadata = DefaultProjectResolver.INSTANCE.getProjectMetadata(clusterState); - ProjectMetadata.Builder projectMetadataBuilder = ProjectMetadata.builder(projectMetadata); - if (PhaseCacheManagement.updateIndicesForPolicy( - projectMetadataBuilder, - projectMetadata, - xContentRegistry, - client, - oldPolicy, - newPolicy, - licenseState - )) { - return ClusterState.builder(clusterState).putProjectMetadata(projectMetadataBuilder).build(); + ProjectMetadata.Builder builder = ProjectMetadata.builder(project); + if (PhaseCacheManagement.updateIndicesForPolicy(builder, project, xContentRegistry, client, oldPolicy, newPolicy, licenseState)) { + return builder.build(); } - return clusterState; - } - - public static ClusterState refreshPhaseDefinition( - final ClusterState clusterState, - final String index, - final LifecyclePolicyMetadata updatedPolicy - ) { - ProjectMetadata projectMetadata = DefaultProjectResolver.INSTANCE.getProjectMetadata(clusterState); - ProjectMetadata newProjectMetadata = PhaseCacheManagement.refreshPhaseDefinition(projectMetadata, index, updatedPolicy); - return ClusterState.builder(clusterState).putProjectMetadata(newProjectMetadata).build(); + return project; } } From 8c423ce0c4320b0d643de9ce18a27f583d3dc357 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Fri, 6 Jun 2025 12:07:32 -0400 Subject: [PATCH 008/102] Vector test tools (#128934) This adds some testing tools for verifying vector recall and latency directly without having to spin up an entire ES node and running a rally track. Its pretty barebones and takes inspiration from lucene-util, but I wanted access to our own formats and tooling to make our lives easier. Here is an example config file. This will build the initial index, run queries at num_candidates: 50, then again at num_candidates 100 (without reindexing, and re-using the cached nearest neighbors). ``` [{ "doc_vectors" : "path", "query_vectors" : "path", "num_docs" : 10000, "num_queries" : 10, "index_type" : "hnsw", "num_candidates" : 50, "k" : 10, "hnsw_m" : 16, "hnsw_ef_construction" : 200, "index_threads" : 4, "reindex" : true, "force_merge" : false, "vector_space" : "maximum_inner_product", "dimensions" : 768 }, { "doc_vectors" : "path", "query_vectors" : "path", "num_docs" : 10000, "num_queries" : 10, "index_type" : "hnsw", "num_candidates" : 100, "k" : 10, "hnsw_m" : 16, "hnsw_ef_construction" : 200, "vector_space" : "maximum_inner_product", "dimensions" : 768 } ] ``` To execute: ``` ./gradlew :qa:vector:checkVec --args="/Path/to/knn_tester_config.json" ``` Calling `./gradlew :qa:vector:checkVecHelp` gives some guidance on how to use it, additionally providing a way to run it via java directly (useful to bypass gradlew guff). --- qa/vector/build.gradle | 101 ++++ qa/vector/licenses/lucene-LICENSE.txt | 475 +++++++++++++++++ qa/vector/licenses/lucene-NOTICE.txt | 192 +++++++ qa/vector/src/main/java/module-info.java | 20 + .../elasticsearch/test/knn/CmdLineArgs.java | 292 +++++++++++ .../test/knn/KnnIndexTester.java | 399 ++++++++++++++ .../elasticsearch/test/knn/KnnIndexer.java | 329 ++++++++++++ .../elasticsearch/test/knn/KnnSearcher.java | 488 ++++++++++++++++++ server/src/main/java/module-info.java | 2 + .../index/codec/vectors/IVFVectorsReader.java | 1 + .../index/codec/vectors/IVFVectorsWriter.java | 1 + .../ES816BinaryQuantizedVectorsReader.java | 1 + .../DirectIOLucene99FlatVectorsReader.java | 1 + .../ES818BinaryQuantizedVectorsReader.java | 1 + .../ES818BinaryQuantizedVectorsWriter.java | 1 + settings.gradle | 2 + test/external-modules/build.gradle | 12 +- 17 files changed, 2312 insertions(+), 6 deletions(-) create mode 100644 qa/vector/build.gradle create mode 100644 qa/vector/licenses/lucene-LICENSE.txt create mode 100644 qa/vector/licenses/lucene-NOTICE.txt create mode 100644 qa/vector/src/main/java/module-info.java create mode 100644 qa/vector/src/main/java/org/elasticsearch/test/knn/CmdLineArgs.java create mode 100644 qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java create mode 100644 qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java create mode 100644 qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java diff --git a/qa/vector/build.gradle b/qa/vector/build.gradle new file mode 100644 index 0000000000000..52b2eeae8226d --- /dev/null +++ b/qa/vector/build.gradle @@ -0,0 +1,101 @@ +/* + * 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". + */ + +apply plugin: 'elasticsearch.java' +apply plugin: 'elasticsearch.build' + + +tasks.named("dependencyLicenses").configure { + mapping from: /lucene-.*/, to: 'lucene' +} + +tasks.named('forbiddenApisMain').configure { + enabled = false +} + +dependencies { + api "org.apache.lucene:lucene-core:${versions.lucene}" + api "org.apache.lucene:lucene-queries:${versions.lucene}" + api "org.apache.lucene:lucene-codecs:${versions.lucene}" + implementation project(':libs:logging') + implementation project(':server') +} +/** + * Task to run the KnnIndexTester with the provided parameters. + */ +tasks.register("checkVec", JavaExec) { + group = "Execution" + description = "Runs KnnIndexTester with the provided parameters to validate recall and performance." + classpath = sourceSets.main.runtimeClasspath + mainClass.set("org.elasticsearch.test.knn.KnnIndexTester") + // Configure logging to console + systemProperty "es.logger.out", "console" + systemProperty "es.logger.level", "INFO" // Change to DEBUG if needed + + if (buildParams.getRuntimeJavaVersion().map { it.majorVersion.toInteger() }.get() >= 21) { + jvmArgs '-Xms4g', '-Xmx4g', '--add-modules=jdk.incubator.vector', '--enable-native-access=ALL-UNNAMED', '-Djava.util.concurrent.ForkJoinPool.common.parallelism=8', '-XX:+UnlockDiagnosticVMOptions', '-XX:+DebugNonSafepoints', '-XX:+HeapDumpOnOutOfMemoryError' + } +} + +tasks.register("checkVecHelp", JavaExec) { + group = "Help" + description = "Prints help for the KnnIndexTester task." + classpath = sourceSets.main.runtimeClasspath + mainClass.set("org.elasticsearch.test.knn.KnnIndexTester") + args = ["--help"] + doLast { + println """ + ============================================================================= + KnnIndexTester Help + ============================================================================= + + Run with Gradle: + ---------------- + # Using default configuration file + ./gradlew :qa:vector:checkVec + + # Using custom configuration file + ./gradlew :qa:vector:checkVec --args="path/to/your/config.json" + + # Adjust heap size + ./gradlew :qa:vector:checkVec -Dorg.gradle.jvmargs="-Xmx8g" --args="path/to/your/config.json" + + # Set environment variable for more extensive JVM options + export GRADLE_OPTS="-Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=100" + ./gradlew :qa:vector:checkVec + + + Run directly with Java: + ---------------------- + # Generate classpath (run once to create the file) + ./gradlew :qa:vector:printClasspath + + # Then use the classpath file with java + java -cp "\$(cat build/vector_classpath.txt)" \\ + --add-modules=jdk.incubator.vector \\ + --enable-native-access=ALL-UNNAMED \\ + -Djava.util.concurrent.ForkJoinPool.common.parallelism=8 \\ + -Xmx4g \\ + -Xms4g \\\\ + org.elasticsearch.test.knn.KnnIndexTester path/to/your/config.json + """ + } +} + +tasks.register("printClasspath") { + group = "Help" + description = "Prints the classpath needed to run KnnIndexTester directly with java" + + doLast { + def classpathFile = new File("${buildDir}/vector_classpath.txt") + classpathFile.parentFile.mkdirs() + classpathFile.text = sourceSets.main.runtimeClasspath.asPath + println "Classpath written to: ${classpathFile.absolutePath}" + } +} diff --git a/qa/vector/licenses/lucene-LICENSE.txt b/qa/vector/licenses/lucene-LICENSE.txt new file mode 100644 index 0000000000000..28b134f5f8e4d --- /dev/null +++ b/qa/vector/licenses/lucene-LICENSE.txt @@ -0,0 +1,475 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + + + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from unicode conversion examples available at +http://www.unicode.org/Public/PROGRAMS/CVTUTF. Here is the copyright +from those sources: + +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + + +Some code in core/src/java/org/apache/lucene/util/ArrayUtil.java was +derived from Python 2.4.2 sources available at +http://www.python.org. Full license is here: + + http://www.python.org/download/releases/2.4.2/license/ + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from Python 3.1.2 sources available at +http://www.python.org. Full license is here: + + http://www.python.org/download/releases/3.1.2/license/ + +Some code in core/src/java/org/apache/lucene/util/automaton was +derived from Brics automaton sources available at +www.brics.dk/automaton/. Here is the copyright from those sources: + +/* + * Copyright (c) 2001-2009 Anders Moeller + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +The levenshtein automata tables in core/src/java/org/apache/lucene/util/automaton +were automatically generated with the moman/finenight FSA package. +Here is the copyright for those sources: + +# Copyright (c) 2010, Jean-Philippe Barrette-LaPierre, +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +Some code in core/src/java/org/apache/lucene/util/UnicodeUtil.java was +derived from ICU (http://www.icu-project.org) +The full license is available here: + http://source.icu-project.org/repos/icu/icu/trunk/license.html + +/* + * Copyright (C) 1999-2010, International Business Machines + * Corporation and others. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * provided that the above copyright notice(s) and this permission notice appear + * in all copies of the Software and that both the above copyright notice(s) and + * this permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE + * LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall not + * be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + */ + +The following license applies to the Snowball stemmers: + +Copyright (c) 2001, Dr Martin Porter +Copyright (c) 2002, Richard Boulton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holders nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The following license applies to the KStemmer: + +Copyright © 2003, +Center for Intelligent Information Retrieval, +University of Massachusetts, Amherst. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. The names "Center for Intelligent Information Retrieval" and +"University of Massachusetts" must not be used to endorse or promote products +derived from this software without prior written permission. To obtain +permission, contact info@ciir.cs.umass.edu. + +THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF MASSACHUSETTS AND OTHER CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +The following license applies to the Morfologik project: + +Copyright (c) 2006 Dawid Weiss +Copyright (c) 2007-2011 Dawid Weiss, Marcin Miłkowski +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Morfologik nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +The dictionary comes from Morfologik project. Morfologik uses data from +Polish ispell/myspell dictionary hosted at http://www.sjp.pl/slownik/en/ and +is licenced on the terms of (inter alia) LGPL and Creative Commons +ShareAlike. The part-of-speech tags were added in Morfologik project and +are not found in the data from sjp.pl. The tagset is similar to IPI PAN +tagset. + +--- + +The following license applies to the Morfeusz project, +used by org.apache.lucene.analysis.morfologik. + +BSD-licensed dictionary of Polish (SGJP) +http://sgjp.pl/morfeusz/ + +Copyright © 2011 Zygmunt Saloni, Włodzimierz Gruszczyński, + Marcin Woliński, Robert Wołosz + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS “AS IS” AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/qa/vector/licenses/lucene-NOTICE.txt b/qa/vector/licenses/lucene-NOTICE.txt new file mode 100644 index 0000000000000..1a1d51572432a --- /dev/null +++ b/qa/vector/licenses/lucene-NOTICE.txt @@ -0,0 +1,192 @@ +Apache Lucene +Copyright 2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +Includes software from other Apache Software Foundation projects, +including, but not limited to: + - Apache Ant + - Apache Jakarta Regexp + - Apache Commons + - Apache Xerces + +ICU4J, (under analysis/icu) is licensed under an MIT styles license +and Copyright (c) 1995-2008 International Business Machines Corporation and others + +Some data files (under analysis/icu/src/data) are derived from Unicode data such +as the Unicode Character Database. See http://unicode.org/copyright.html for more +details. + +Brics Automaton (under core/src/java/org/apache/lucene/util/automaton) is +BSD-licensed, created by Anders Møller. See http://www.brics.dk/automaton/ + +The levenshtein automata tables (under core/src/java/org/apache/lucene/util/automaton) were +automatically generated with the moman/finenight FSA library, created by +Jean-Philippe Barrette-LaPierre. This library is available under an MIT license, +see http://sites.google.com/site/rrettesite/moman and +http://bitbucket.org/jpbarrette/moman/overview/ + +The class org.apache.lucene.util.WeakIdentityMap was derived from +the Apache CXF project and is Apache License 2.0. + +The Google Code Prettify is Apache License 2.0. +See http://code.google.com/p/google-code-prettify/ + +JUnit (junit-4.10) is licensed under the Common Public License v. 1.0 +See http://junit.sourceforge.net/cpl-v10.html + +This product includes code (JaspellTernarySearchTrie) from Java Spelling Checkin +g Package (jaspell): http://jaspell.sourceforge.net/ +License: The BSD License (http://www.opensource.org/licenses/bsd-license.php) + +The snowball stemmers in + analysis/common/src/java/net/sf/snowball +were developed by Martin Porter and Richard Boulton. +The snowball stopword lists in + analysis/common/src/resources/org/apache/lucene/analysis/snowball +were developed by Martin Porter and Richard Boulton. +The full snowball package is available from + http://snowball.tartarus.org/ + +The KStem stemmer in + analysis/common/src/org/apache/lucene/analysis/en +was developed by Bob Krovetz and Sergio Guzman-Lara (CIIR-UMass Amherst) +under the BSD-license. + +The Arabic,Persian,Romanian,Bulgarian, Hindi and Bengali analyzers (common) come with a default +stopword list that is BSD-licensed created by Jacques Savoy. These files reside in: +analysis/common/src/resources/org/apache/lucene/analysis/ar/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/fa/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/ro/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/bg/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/hi/stopwords.txt, +analysis/common/src/resources/org/apache/lucene/analysis/bn/stopwords.txt +See http://members.unine.ch/jacques.savoy/clef/index.html. + +The German,Spanish,Finnish,French,Hungarian,Italian,Portuguese,Russian and Swedish light stemmers +(common) are based on BSD-licensed reference implementations created by Jacques Savoy and +Ljiljana Dolamic. These files reside in: +analysis/common/src/java/org/apache/lucene/analysis/de/GermanLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/de/GermanMinimalStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/es/SpanishLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fi/FinnishLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fr/FrenchLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/fr/FrenchMinimalStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/hu/HungarianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/it/ItalianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/pt/PortugueseLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/ru/RussianLightStemmer.java +analysis/common/src/java/org/apache/lucene/analysis/sv/SwedishLightStemmer.java + +The Stempel analyzer (stempel) includes BSD-licensed software developed +by the Egothor project http://egothor.sf.net/, created by Leo Galambos, Martin Kvapil, +and Edmond Nolan. + +The Polish analyzer (stempel) comes with a default +stopword list that is BSD-licensed created by the Carrot2 project. The file resides +in stempel/src/resources/org/apache/lucene/analysis/pl/stopwords.txt. +See http://project.carrot2.org/license.html. + +The SmartChineseAnalyzer source code (smartcn) was +provided by Xiaoping Gao and copyright 2009 by www.imdict.net. + +WordBreakTestUnicode_*.java (under modules/analysis/common/src/test/) +is derived from Unicode data such as the Unicode Character Database. +See http://unicode.org/copyright.html for more details. + +The Morfologik analyzer (morfologik) includes BSD-licensed software +developed by Dawid Weiss and Marcin Miłkowski (http://morfologik.blogspot.com/). + +Morfologik uses data from Polish ispell/myspell dictionary +(http://www.sjp.pl/slownik/en/) licenced on the terms of (inter alia) +LGPL and Creative Commons ShareAlike. + +Morfologic includes data from BSD-licensed dictionary of Polish (SGJP) +(http://sgjp.pl/morfeusz/) + +Servlet-api.jar and javax.servlet-*.jar are under the CDDL license, the original +source code for this can be found at http://www.eclipse.org/jetty/downloads.php + +=========================================================================== +Kuromoji Japanese Morphological Analyzer - Apache Lucene Integration +=========================================================================== + +This software includes a binary and/or source version of data from + + mecab-ipadic-2.7.0-20070801 + +which can be obtained from + + http://atilika.com/releases/mecab-ipadic/mecab-ipadic-2.7.0-20070801.tar.gz + +or + + http://jaist.dl.sourceforge.net/project/mecab/mecab-ipadic/2.7.0-20070801/mecab-ipadic-2.7.0-20070801.tar.gz + +=========================================================================== +mecab-ipadic-2.7.0-20070801 Notice +=========================================================================== + +Nara Institute of Science and Technology (NAIST), +the copyright holders, disclaims all warranties with regard to this +software, including all implied warranties of merchantability and +fitness, in no event shall NAIST be liable for +any special, indirect or consequential damages or any damages +whatsoever resulting from loss of use, data or profits, whether in an +action of contract, negligence or other tortuous action, arising out +of or in connection with the use or performance of this software. + +A large portion of the dictionary entries +originate from ICOT Free Software. The following conditions for ICOT +Free Software applies to the current dictionary as well. + +Each User may also freely distribute the Program, whether in its +original form or modified, to any third party or parties, PROVIDED +that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear +on, or be attached to, the Program, which is distributed substantially +in the same form as set out herein and that such intended +distribution, if actually made, will neither violate or otherwise +contravene any of the laws and regulations of the countries having +jurisdiction over the User or the intended distribution itself. + +NO WARRANTY + +The program was produced on an experimental basis in the course of the +research and development conducted during the project and is provided +to users as so produced on an experimental basis. Accordingly, the +program is provided without any warranty whatsoever, whether express, +implied, statutory or otherwise. The term "warranty" used herein +includes, but is not limited to, any warranty of the quality, +performance, merchantability and fitness for a particular purpose of +the program and the nonexistence of any infringement or violation of +any right of any third party. + +Each user of the program will agree and understand, and be deemed to +have agreed and understood, that there is no warranty whatsoever for +the program and, accordingly, the entire risk arising from or +otherwise connected with the program is assumed by the user. + +Therefore, neither ICOT, the copyright holder, or any other +organization that participated in or was otherwise related to the +development of the program and their respective officials, directors, +officers and other employees shall be held liable for any and all +damages, including, without limitation, general, special, incidental +and consequential damages, arising out of or otherwise in connection +with the use or inability to use the program or any product, material +or result produced or otherwise obtained by using the program, +regardless of whether they have been advised of, or otherwise had +knowledge of, the possibility of such damages at any time during the +project or thereafter. Each user will be deemed to have agreed to the +foregoing by his or her commencement of use of the program. The term +"use" as used herein includes, but is not limited to, the use, +modification, copying and distribution of the program and the +production of secondary products from the program. + +In the case where the program, whether in its original form or +modified, was distributed or delivered to or received by a user from +any person, organization or entity other than ICOT, unless it makes or +grants independently of ICOT any specific warranty to the user in +writing, such person, organization or entity, will also be exempted +from and not be held liable to the user for any such damages as noted +above as far as the program is concerned. diff --git a/qa/vector/src/main/java/module-info.java b/qa/vector/src/main/java/module-info.java new file mode 100644 index 0000000000000..9b9f585559688 --- /dev/null +++ b/qa/vector/src/main/java/module-info.java @@ -0,0 +1,20 @@ +/* + * 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". + */ + +module org.elasticsearch.test.knn { + requires org.elasticsearch.base; + requires org.elasticsearch.server; + requires org.elasticsearch.xcontent; + requires org.apache.lucene.core; + requires org.apache.lucene.codecs; + requires org.apache.lucene.queries; + requires org.elasticsearch.logging; + requires java.management; + requires jdk.management; +} diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/CmdLineArgs.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/CmdLineArgs.java new file mode 100644 index 0000000000000..a5f66ce5ea837 --- /dev/null +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/CmdLineArgs.java @@ -0,0 +1,292 @@ +/* + * 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.test.knn; + +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.elasticsearch.common.Strings; +import org.elasticsearch.core.PathUtils; +import org.elasticsearch.xcontent.ObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Locale; + +/** + * Command line arguments for the KNN index tester. + * This class encapsulates all the parameters required to run the KNN index tests. + */ +record CmdLineArgs( + Path docVectors, + Path queryVectors, + int numDocs, + int numQueries, + KnnIndexTester.IndexType indexType, + int numCandidates, + int k, + int nProbe, + int ivfClusterSize, + int overSamplingFactor, + int hnswM, + int hnswEfConstruction, + int searchThreads, + int indexThreads, + boolean reindex, + boolean forceMerge, + VectorSimilarityFunction vectorSpace, + int quantizeBits, + VectorEncoding vectorEncoding, + int dimensions +) implements ToXContentObject { + + static final ParseField DOC_VECTORS_FIELD = new ParseField("doc_vectors"); + static final ParseField QUERY_VECTORS_FIELD = new ParseField("query_vectors"); + static final ParseField NUM_DOCS_FIELD = new ParseField("num_docs"); + static final ParseField NUM_QUERIES_FIELD = new ParseField("num_queries"); + static final ParseField INDEX_TYPE_FIELD = new ParseField("index_type"); + static final ParseField NUM_CANDIDATES_FIELD = new ParseField("num_candidates"); + static final ParseField K_FIELD = new ParseField("k"); + static final ParseField N_PROBE_FIELD = new ParseField("n_probe"); + static final ParseField IVF_CLUSTER_SIZE_FIELD = new ParseField("ivf_cluster_size"); + static final ParseField OVER_SAMPLING_FACTOR_FIELD = new ParseField("over_sampling_factor"); + static final ParseField HNSW_M_FIELD = new ParseField("hnsw_m"); + static final ParseField HNSW_EF_CONSTRUCTION_FIELD = new ParseField("hnsw_ef_construction"); + static final ParseField SEARCH_THREADS_FIELD = new ParseField("search_threads"); + static final ParseField INDEX_THREADS_FIELD = new ParseField("index_threads"); + static final ParseField REINDEX_FIELD = new ParseField("reindex"); + static final ParseField FORCE_MERGE_FIELD = new ParseField("force_merge"); + static final ParseField VECTOR_SPACE_FIELD = new ParseField("vector_space"); + static final ParseField QUANTIZE_BITS_FIELD = new ParseField("quantize_bits"); + static final ParseField VECTOR_ENCODING_FIELD = new ParseField("vector_encoding"); + static final ParseField DIMENSIONS_FIELD = new ParseField("dimensions"); + + static CmdLineArgs fromXContent(XContentParser parser) throws IOException { + Builder builder = PARSER.apply(parser, null); + return builder.build(); + } + + static final ObjectParser PARSER = new ObjectParser<>("cmd_line_args", true, Builder::new); + + static { + PARSER.declareString(Builder::setDocVectors, DOC_VECTORS_FIELD); + PARSER.declareString(Builder::setQueryVectors, QUERY_VECTORS_FIELD); + PARSER.declareInt(Builder::setNumDocs, NUM_DOCS_FIELD); + PARSER.declareInt(Builder::setNumQueries, NUM_QUERIES_FIELD); + PARSER.declareString(Builder::setIndexType, INDEX_TYPE_FIELD); + PARSER.declareInt(Builder::setNumCandidates, NUM_CANDIDATES_FIELD); + PARSER.declareInt(Builder::setK, K_FIELD); + PARSER.declareInt(Builder::setNProbe, N_PROBE_FIELD); + PARSER.declareInt(Builder::setIvfClusterSize, IVF_CLUSTER_SIZE_FIELD); + PARSER.declareInt(Builder::setOverSamplingFactor, OVER_SAMPLING_FACTOR_FIELD); + PARSER.declareInt(Builder::setHnswM, HNSW_M_FIELD); + PARSER.declareInt(Builder::setHnswEfConstruction, HNSW_EF_CONSTRUCTION_FIELD); + PARSER.declareInt(Builder::setSearchThreads, SEARCH_THREADS_FIELD); + PARSER.declareInt(Builder::setIndexThreads, INDEX_THREADS_FIELD); + PARSER.declareBoolean(Builder::setReindex, REINDEX_FIELD); + PARSER.declareBoolean(Builder::setForceMerge, FORCE_MERGE_FIELD); + PARSER.declareString(Builder::setVectorSpace, VECTOR_SPACE_FIELD); + PARSER.declareInt(Builder::setQuantizeBits, QUANTIZE_BITS_FIELD); + PARSER.declareString(Builder::setVectorEncoding, VECTOR_ENCODING_FIELD); + PARSER.declareInt(Builder::setDimensions, DIMENSIONS_FIELD); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (docVectors != null) { + builder.field(DOC_VECTORS_FIELD.getPreferredName(), docVectors.toString()); + } + if (queryVectors != null) { + builder.field(QUERY_VECTORS_FIELD.getPreferredName(), queryVectors.toString()); + } + builder.field(NUM_DOCS_FIELD.getPreferredName(), numDocs); + builder.field(NUM_QUERIES_FIELD.getPreferredName(), numQueries); + builder.field(INDEX_TYPE_FIELD.getPreferredName(), indexType.name().toLowerCase(Locale.ROOT)); + builder.field(NUM_CANDIDATES_FIELD.getPreferredName(), numCandidates); + builder.field(K_FIELD.getPreferredName(), k); + builder.field(N_PROBE_FIELD.getPreferredName(), nProbe); + builder.field(IVF_CLUSTER_SIZE_FIELD.getPreferredName(), ivfClusterSize); + builder.field(OVER_SAMPLING_FACTOR_FIELD.getPreferredName(), overSamplingFactor); + builder.field(HNSW_M_FIELD.getPreferredName(), hnswM); + builder.field(HNSW_EF_CONSTRUCTION_FIELD.getPreferredName(), hnswEfConstruction); + builder.field(SEARCH_THREADS_FIELD.getPreferredName(), searchThreads); + builder.field(INDEX_THREADS_FIELD.getPreferredName(), indexThreads); + builder.field(REINDEX_FIELD.getPreferredName(), reindex); + builder.field(FORCE_MERGE_FIELD.getPreferredName(), forceMerge); + builder.field(VECTOR_SPACE_FIELD.getPreferredName(), vectorSpace.name().toLowerCase(Locale.ROOT)); + builder.field(QUANTIZE_BITS_FIELD.getPreferredName(), quantizeBits); + builder.field(VECTOR_ENCODING_FIELD.getPreferredName(), vectorEncoding.name().toLowerCase(Locale.ROOT)); + builder.field(DIMENSIONS_FIELD.getPreferredName(), dimensions); + return builder.endObject(); + } + + @Override + public String toString() { + return Strings.toString(this, false, false); + } + + static class Builder { + private Path docVectors; + private Path queryVectors; + private int numDocs = 1000; + private int numQueries = 100; + private KnnIndexTester.IndexType indexType = KnnIndexTester.IndexType.HNSW; + private int numCandidates = 1000; + private int k = 10; + private int nProbe = 10; + private int ivfClusterSize = 1000; + private int overSamplingFactor = 1; + private int hnswM = 16; + private int hnswEfConstruction = 200; + private int searchThreads = 1; + private int indexThreads = 1; + private boolean reindex = false; + private boolean forceMerge = false; + private VectorSimilarityFunction vectorSpace = VectorSimilarityFunction.EUCLIDEAN; + private int quantizeBits = 8; + private VectorEncoding vectorEncoding = VectorEncoding.FLOAT32; + private int dimensions; + + public Builder setDocVectors(String docVectors) { + this.docVectors = PathUtils.get(docVectors); + return this; + } + + public Builder setQueryVectors(String queryVectors) { + this.queryVectors = PathUtils.get(queryVectors); + return this; + } + + public Builder setNumDocs(int numDocs) { + this.numDocs = numDocs; + return this; + } + + public Builder setNumQueries(int numQueries) { + this.numQueries = numQueries; + return this; + } + + public Builder setIndexType(String indexType) { + this.indexType = KnnIndexTester.IndexType.valueOf(indexType.toUpperCase(Locale.ROOT)); + return this; + } + + public Builder setNumCandidates(int numCandidates) { + this.numCandidates = numCandidates; + return this; + } + + public Builder setK(int k) { + this.k = k; + return this; + } + + public Builder setNProbe(int nProbe) { + this.nProbe = nProbe; + return this; + } + + public Builder setIvfClusterSize(int ivfClusterSize) { + this.ivfClusterSize = ivfClusterSize; + return this; + } + + public Builder setOverSamplingFactor(int overSamplingFactor) { + this.overSamplingFactor = overSamplingFactor; + return this; + } + + public Builder setHnswM(int hnswM) { + this.hnswM = hnswM; + return this; + } + + public Builder setHnswEfConstruction(int hnswEfConstruction) { + this.hnswEfConstruction = hnswEfConstruction; + return this; + } + + public Builder setSearchThreads(int searchThreads) { + this.searchThreads = searchThreads; + return this; + } + + public Builder setIndexThreads(int indexThreads) { + this.indexThreads = indexThreads; + return this; + } + + public Builder setReindex(boolean reindex) { + this.reindex = reindex; + return this; + } + + public Builder setForceMerge(boolean forceMerge) { + this.forceMerge = forceMerge; + return this; + } + + public Builder setVectorSpace(String vectorSpace) { + this.vectorSpace = VectorSimilarityFunction.valueOf(vectorSpace.toUpperCase(Locale.ROOT)); + return this; + } + + public Builder setQuantizeBits(int quantizeBits) { + this.quantizeBits = quantizeBits; + return this; + } + + public Builder setVectorEncoding(String vectorEncoding) { + this.vectorEncoding = VectorEncoding.valueOf(vectorEncoding.toUpperCase(Locale.ROOT)); + return this; + } + + public Builder setDimensions(int dimensions) { + this.dimensions = dimensions; + return this; + } + + public CmdLineArgs build() { + if (docVectors == null) { + throw new IllegalArgumentException("Document vectors path must be provided"); + } + if (dimensions <= 0) { + throw new IllegalArgumentException("dimensions must be a positive integer"); + } + return new CmdLineArgs( + docVectors, + queryVectors, + numDocs, + numQueries, + indexType, + numCandidates, + k, + nProbe, + ivfClusterSize, + overSamplingFactor, + hnswM, + hnswEfConstruction, + searchThreads, + indexThreads, + reindex, + forceMerge, + vectorSpace, + quantizeBits, + vectorEncoding, + dimensions + ); + } + } +} diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java new file mode 100644 index 0000000000000..6aa2e051bacc2 --- /dev/null +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java @@ -0,0 +1,399 @@ +/* + * 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.test.knn; + +import com.sun.management.ThreadMXBean; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.lucene101.Lucene101Codec; +import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.logging.LogConfigurator; +import org.elasticsearch.core.PathUtils; +import org.elasticsearch.index.codec.vectors.ES813Int8FlatVectorFormat; +import org.elasticsearch.index.codec.vectors.ES814HnswScalarQuantizedVectorsFormat; +import org.elasticsearch.index.codec.vectors.IVFVectorsFormat; +import org.elasticsearch.index.codec.vectors.es818.ES818BinaryQuantizedVectorsFormat; +import org.elasticsearch.index.codec.vectors.es818.ES818HnswBinaryQuantizedVectorsFormat; +import org.elasticsearch.logging.Level; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; + +import java.io.InputStream; +import java.lang.management.ThreadInfo; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * A utility class to create and test KNN indices using Lucene. + * It supports various index types (HNSW, FLAT, IVF) and configurations. + */ +public class KnnIndexTester { + static final Level LOG_LEVEL = Level.DEBUG; + + static final SysOutLogger logger = new SysOutLogger(); + + static { + LogConfigurator.loadLog4jPlugins(); + LogConfigurator.configureESLogging(); // native access requires logging to be initialized + } + + static final String INDEX_DIR = "target/knn_index"; + + enum IndexType { + HNSW, + FLAT, + IVF + } + + private static String formatIndexPath(CmdLineArgs args) { + List suffix = new ArrayList<>(); + if (args.indexType() == IndexType.FLAT) { + suffix.add("flat"); + } else if (args.indexType() == IndexType.IVF) { + suffix.add("ivf"); + suffix.add(Integer.toString(args.ivfClusterSize())); + } else { + suffix.add(Integer.toString(args.hnswM())); + suffix.add(Integer.toString(args.hnswEfConstruction())); + if (args.quantizeBits() < 32) { + suffix.add(Integer.toString(args.quantizeBits())); + } + } + return INDEX_DIR + "/" + args.docVectors().getFileName() + "-" + String.join("-", suffix) + ".index"; + } + + static Codec createCodec(CmdLineArgs args) { + final KnnVectorsFormat format; + if (args.indexType() == IndexType.IVF) { + format = new IVFVectorsFormat(args.ivfClusterSize()); + } else { + if (args.quantizeBits() == 1) { + if (args.indexType() == IndexType.FLAT) { + format = new ES818BinaryQuantizedVectorsFormat(); + } else { + format = new ES818HnswBinaryQuantizedVectorsFormat(args.hnswM(), args.hnswEfConstruction(), 1, null); + } + } else if (args.quantizeBits() < 32) { + if (args.indexType() == IndexType.FLAT) { + format = new ES813Int8FlatVectorFormat(null, args.quantizeBits(), true); + } else { + format = new ES814HnswScalarQuantizedVectorsFormat( + args.hnswM(), + args.hnswEfConstruction(), + null, + args.quantizeBits(), + true + ); + } + } else { + format = new Lucene99HnswVectorsFormat(args.hnswM(), args.hnswEfConstruction(), 1, null); + } + } + return new Lucene101Codec() { + @Override + public KnnVectorsFormat getKnnVectorsFormatForField(String field) { + return format; + } + }; + } + + /** + * Main method to run the KNN index tester. + * It parses command line arguments, creates the index, and runs searches if specified. + * + * @param args Command line arguments + * @throws Exception If an error occurs during index creation or search + */ + public static void main(String[] args) throws Exception { + if (args.length != 1 || args[0].equals("--help") || args[0].equals("-h")) { + // printout an example configuration formatted file and indicate that it is required + System.out.println("Usage: java -cp org.elasticsearch.test.knn.KnnIndexTester "); + System.out.println("Where is a JSON file containing one or more configurations for the KNN index tester."); + System.out.println("An example configuration object: "); + System.out.println( + Strings.toString( + new CmdLineArgs.Builder().setDimensions(64) + .setDocVectors("/doc/vectors/path") + .setQueryVectors("/query/vectors/path") + .build(), + true, + true + ) + ); + return; + } + String jsonConfig = args[0]; + // Parse command line arguments + Path jsonConfigPath = PathUtils.get(jsonConfig); + if (Files.exists(jsonConfigPath) == false) { + throw new IllegalArgumentException("JSON config file does not exist: " + jsonConfigPath); + } + // Parse the JSON config file to get command line arguments + // This assumes that CmdLineArgs.fromXContent is implemented to parse the JSON file + List cmdLineArgsList = new ArrayList<>(); + try ( + InputStream jsonStream = Files.newInputStream(jsonConfigPath); + XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, jsonStream) + ) { + // check if the parser is at the start of an object if so, we only have one set of arguments + if (parser.currentToken() == null && parser.nextToken() == XContentParser.Token.START_OBJECT) { + cmdLineArgsList.add(CmdLineArgs.fromXContent(parser)); + } else if (parser.currentToken() == XContentParser.Token.START_ARRAY) { + // if the parser is at the start of an array, we have multiple sets of arguments + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + cmdLineArgsList.add(CmdLineArgs.fromXContent(parser)); + } + } else { + throw new IllegalArgumentException("Invalid JSON format in config file: " + jsonConfigPath); + } + } + FormattedResults formattedResults = new FormattedResults(); + for (CmdLineArgs cmdLineArgs : cmdLineArgsList) { + Results result = new Results(cmdLineArgs.indexType().name().toLowerCase(Locale.ROOT), cmdLineArgs.numDocs()); + System.out.println("Running KNN index tester with arguments: " + cmdLineArgs); + Codec codec = createCodec(cmdLineArgs); + Path indexPath = PathUtils.get(formatIndexPath(cmdLineArgs)); + if (cmdLineArgs.reindex() || cmdLineArgs.forceMerge()) { + KnnIndexer knnIndexer = new KnnIndexer( + cmdLineArgs.docVectors(), + indexPath, + codec, + cmdLineArgs.indexThreads(), + cmdLineArgs.vectorEncoding(), + cmdLineArgs.dimensions(), + cmdLineArgs.vectorSpace(), + cmdLineArgs.numDocs() + ); + if (Files.exists(indexPath) == false) { + if (cmdLineArgs.reindex() == false) { + throw new IllegalArgumentException("Index path does not exist: " + indexPath); + } + if (cmdLineArgs.forceMerge()) { + throw new IllegalArgumentException("Force merging without an existing index in: " + indexPath); + } + } + if (cmdLineArgs.reindex()) { + knnIndexer.createIndex(result); + } + if (cmdLineArgs.forceMerge()) { + knnIndexer.forceMerge(result); + } else { + knnIndexer.numSegments(result); + } + } + if (cmdLineArgs.queryVectors() != null) { + KnnSearcher knnSearcher = new KnnSearcher(indexPath, cmdLineArgs); + knnSearcher.runSearch(result); + } + formattedResults.results.add(result); + } + System.out.println("Results:"); + System.out.println(formattedResults); + } + + static class FormattedResults { + List results = new ArrayList<>(); + + @Override + public String toString() { + if (results.isEmpty()) { + return "No results available."; + } + + // Define column headers + String[] headers = { + "index_type", + "num_docs", + "index_time(ms)", + "force_merge_time(ms)", + "num_segments", + "latency(ms)", + "net_cpu_time(ms)", + "avg_cpu_count", + "QPS", + "recall", + "visited" }; + + // Calculate appropriate column widths based on headers and data + int[] widths = calculateColumnWidths(headers); + + StringBuilder sb = new StringBuilder(); + + // Format and append header + sb.append(formatRow(headers, widths)); + sb.append("\n"); + + // Add separator line + for (int width : widths) { + sb.append("-".repeat(width)).append(" "); + } + sb.append("\n"); + + // Format and append each row of data + for (Results result : results) { + String[] rowData = { + result.indexType, + Integer.toString(result.numDocs), + Long.toString(result.indexTimeMS), + Long.toString(result.forceMergeTimeMS), + Integer.toString(result.numSegments), + String.format(Locale.ROOT, "%.2f", result.avgLatency), + String.format(Locale.ROOT, "%.2f", result.netCpuTimeMS), + String.format(Locale.ROOT, "%.2f", result.avgCpuCount), + String.format(Locale.ROOT, "%.2f", result.qps), + String.format(Locale.ROOT, "%.2f", result.avgRecall), + String.format(Locale.ROOT, "%.2f", result.averageVisited) }; + sb.append(formatRow(rowData, widths)); + sb.append("\n"); + } + + return sb.toString(); + } + + // Helper method to format a single row with proper column widths + private String formatRow(String[] values, int[] widths) { + StringBuilder row = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + // Left-align text column (index_type), right-align numeric columns + String format = (i == 0) ? "%-" + widths[i] + "s" : "%" + widths[i] + "s"; + row.append(Strings.format(format, values[i])); + + // Add separation between columns + if (i < values.length - 1) { + row.append(" "); + } + } + return row.toString(); + } + + // Calculate appropriate column widths based on headers and data + private int[] calculateColumnWidths(String[] headers) { + int[] widths = new int[headers.length]; + + // Initialize widths with header lengths + for (int i = 0; i < headers.length; i++) { + widths[i] = headers[i].length(); + } + + // Update widths based on data + for (Results result : results) { + String[] values = { + result.indexType, + Integer.toString(result.numDocs), + Long.toString(result.indexTimeMS), + Long.toString(result.forceMergeTimeMS), + Integer.toString(result.numSegments), + String.format(Locale.ROOT, "%.2f", result.avgLatency), + String.format(Locale.ROOT, "%.2f", result.netCpuTimeMS), + String.format(Locale.ROOT, "%.2f", result.avgCpuCount), + String.format(Locale.ROOT, "%.2f", result.qps), + String.format(Locale.ROOT, "%.2f", result.avgRecall), + String.format(Locale.ROOT, "%.2f", result.averageVisited) }; + + for (int i = 0; i < values.length; i++) { + widths[i] = Math.max(widths[i], values[i].length()); + } + } + + return widths; + } + } + + static class Results { + final String indexType; + final int numDocs; + long indexTimeMS; + long forceMergeTimeMS; + int numSegments; + double avgLatency; + double qps; + double avgRecall; + double averageVisited; + double netCpuTimeMS; + double avgCpuCount; + + Results(String indexType, int numDocs) { + this.indexType = indexType; + this.numDocs = numDocs; + } + } + + static final class SysOutLogger { + + void warn(String message) { + if (LOG_LEVEL.ordinal() >= Level.WARN.ordinal()) { + System.out.println(message); + } + } + + void warn(String message, Object... params) { + if (LOG_LEVEL.ordinal() >= Level.WARN.ordinal()) { + System.out.println(String.format(Locale.ROOT, message, params)); + } + } + + void info(String message) { + if (LOG_LEVEL.ordinal() >= Level.INFO.ordinal()) { + System.out.println(message); + } + } + + void info(String message, Object... params) { + if (LOG_LEVEL.ordinal() >= Level.INFO.ordinal()) { + System.out.println(String.format(Locale.ROOT, message, params)); + } + } + + void debug(String message) { + if (LOG_LEVEL.ordinal() >= Level.DEBUG.ordinal()) { + System.out.println(message); + } + } + + void debug(String message, Object... params) { + if (LOG_LEVEL.ordinal() >= Level.DEBUG.ordinal()) { + System.out.println(String.format(Locale.ROOT, message, params)); + } + } + + void trace(String message) { + if (LOG_LEVEL == Level.TRACE) { + System.out.println(message); + } + } + + void trace(String message, Object... params) { + if (LOG_LEVEL == Level.TRACE) { + System.out.println(String.format(Locale.ROOT, message, params)); + } + } + } + + static final class ThreadDetails { + private static final ThreadMXBean threadBean = (ThreadMXBean) java.lang.management.ManagementFactory.getThreadMXBean(); + public final long[] threadIDs; + public final long[] cpuTimesNS; + public final ThreadInfo[] threadInfos; + public final long ns; + + ThreadDetails() { + ns = System.nanoTime(); + threadIDs = threadBean.getAllThreadIds(); + cpuTimesNS = threadBean.getThreadCpuTime(threadIDs); + threadInfos = threadBean.getThreadInfo(threadIDs); + } + } +} diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java new file mode 100644 index 0000000000000..07ee4975df7e3 --- /dev/null +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java @@ -0,0 +1,329 @@ +/* + * @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. + * a copy and modification from Lucene util + * Modifications copyright (C) 2025 Elasticsearch B.V. + */ + +package org.elasticsearch.test.knn; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.KnnByteVectorField; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.document.StoredField; +import org.apache.lucene.index.ConcurrentMergeScheduler; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.util.PrintStreamInfoStream; +import org.elasticsearch.common.io.Channels; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.elasticsearch.test.knn.KnnIndexTester.logger; + +class KnnIndexer { + private static final double WRITER_BUFFER_MB = 128; + static final String ID_FIELD = "id"; + static final String VECTOR_FIELD = "vector"; + + private final Path docsPath; + private final Path indexPath; + private final VectorEncoding vectorEncoding; + private final int dim; + private final VectorSimilarityFunction similarityFunction; + private final Codec codec; + private final int numDocs; + private final int numIndexThreads; + + KnnIndexer( + Path docsPath, + Path indexPath, + Codec codec, + int numIndexThreads, + VectorEncoding vectorEncoding, + int dim, + VectorSimilarityFunction similarityFunction, + int numDocs + ) { + this.docsPath = docsPath; + this.indexPath = indexPath; + this.codec = codec; + this.numIndexThreads = numIndexThreads; + this.vectorEncoding = vectorEncoding; + this.dim = dim; + this.similarityFunction = similarityFunction; + this.numDocs = numDocs; + } + + void numSegments(KnnIndexTester.Results result) { + try (FSDirectory dir = FSDirectory.open(indexPath); IndexReader reader = DirectoryReader.open(dir)) { + result.numSegments = reader.leaves().size(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to get segment count for index at " + indexPath, e); + } + } + + void createIndex(KnnIndexTester.Results result) throws IOException, InterruptedException, ExecutionException { + IndexWriterConfig iwc = new IndexWriterConfig().setOpenMode(IndexWriterConfig.OpenMode.CREATE); + iwc.setCodec(codec); + iwc.setRAMBufferSizeMB(WRITER_BUFFER_MB); + iwc.setUseCompoundFile(false); + + iwc.setMaxFullFlushMergeWaitMillis(0); + + FieldType fieldType = switch (vectorEncoding) { + case BYTE -> KnnByteVectorField.createFieldType(dim, similarityFunction); + case FLOAT32 -> KnnFloatVectorField.createFieldType(dim, similarityFunction); + }; + iwc.setInfoStream(new PrintStreamInfoStream(System.out) { + @Override + public boolean isEnabled(String component) { + return Objects.equals(component, "IVF"); + } + }); + logger.debug( + "KnnIndexer: using codec=%s, vectorEncoding=%s, dim=%d, similarityFunction=%s", + codec.getName(), + vectorEncoding, + dim, + similarityFunction + ); + + if (Files.exists(indexPath)) { + logger.debug("KnnIndexer: existing index at %s", indexPath); + } else { + Files.createDirectories(indexPath); + } + + long start = System.nanoTime(); + try ( + FSDirectory dir = FSDirectory.open(indexPath); + IndexWriter iw = new IndexWriter(dir, iwc); + FileChannel in = FileChannel.open(docsPath) + ) { + long docsPathSizeInBytes = in.size(); + if (docsPathSizeInBytes % ((long) dim * vectorEncoding.byteSize) != 0) { + throw new IllegalArgumentException( + "docsPath \"" + docsPath + "\" does not contain a whole number of vectors? size=" + docsPathSizeInBytes + ); + } + logger.info( + "docsPathSizeInBytes=%d, dim=%d, vectorEncoding=%s, byteSize=%d", + docsPathSizeInBytes, + dim, + vectorEncoding, + vectorEncoding.byteSize + ); + + VectorReader inReader = VectorReader.create(in, dim, vectorEncoding); + try (ExecutorService exec = Executors.newFixedThreadPool(numIndexThreads, r -> new Thread(r, "KnnIndexer-Thread"))) { + AtomicInteger numDocsIndexed = new AtomicInteger(); + List> threads = new ArrayList<>(); + for (int i = 0; i < numIndexThreads; i++) { + Thread t = new IndexerThread(iw, inReader, dim, vectorEncoding, fieldType, numDocsIndexed, numDocs); + t.setDaemon(true); + threads.add(exec.submit(t)); + } + for (Future t : threads) { + t.get(); + } + } + logger.debug("all indexing threads finished, now IndexWriter.commit()"); + iw.commit(); + ConcurrentMergeScheduler cms = (ConcurrentMergeScheduler) iwc.getMergeScheduler(); + cms.sync(); + } + + long elapsed = System.nanoTime() - start; + logger.debug("Indexing took %d ms for %d docs", TimeUnit.NANOSECONDS.toMillis(elapsed), numDocs); + result.indexTimeMS = TimeUnit.NANOSECONDS.toMillis(elapsed); + } + + void forceMerge(KnnIndexTester.Results results) throws Exception { + IndexWriterConfig iwc = new IndexWriterConfig().setOpenMode(IndexWriterConfig.OpenMode.APPEND); + iwc.setInfoStream(new PrintStreamInfoStream(System.out) { + @Override + public boolean isEnabled(String component) { + return Objects.equals(component, "IVF"); + } + }); + iwc.setCodec(codec); + logger.debug("KnnIndexer: forceMerge in %s", indexPath); + long startNS = System.nanoTime(); + try (IndexWriter iw = new IndexWriter(FSDirectory.open(indexPath), iwc)) { + iw.forceMerge(1); + } + long endNS = System.nanoTime(); + long elapsedNSec = (endNS - startNS); + logger.info("forceMerge took %d ms", TimeUnit.NANOSECONDS.toMillis(elapsedNSec)); + results.forceMergeTimeMS = TimeUnit.NANOSECONDS.toMillis(elapsedNSec); + } + + static class IndexerThread extends Thread { + private final IndexWriter iw; + private final AtomicInteger numDocsIndexed; + private final int numDocsToIndex; + private final FieldType fieldType; + private final VectorEncoding vectorEncoding; + private final byte[] byteVectorBuffer; + private final float[] floatVectorBuffer; + private final VectorReader in; + + private IndexerThread( + IndexWriter iw, + VectorReader in, + int dims, + VectorEncoding vectorEncoding, + FieldType fieldType, + AtomicInteger numDocsIndexed, + int numDocsToIndex + ) { + this.iw = iw; + this.in = in; + this.vectorEncoding = vectorEncoding; + this.fieldType = fieldType; + this.numDocsIndexed = numDocsIndexed; + this.numDocsToIndex = numDocsToIndex; + switch (vectorEncoding) { + case BYTE -> { + byteVectorBuffer = new byte[dims]; + floatVectorBuffer = null; + } + case FLOAT32 -> { + floatVectorBuffer = new float[dims]; + byteVectorBuffer = null; + } + default -> throw new IllegalArgumentException("unexpected vector encoding: " + vectorEncoding); + } + } + + @Override + public void run() { + try { + _run(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + private void _run() throws IOException { + while (true) { + int id = numDocsIndexed.getAndIncrement(); + if (id >= numDocsToIndex) { + break; + } + + Document doc = new Document(); + switch (vectorEncoding) { + case BYTE -> { + in.next(byteVectorBuffer); + doc.add(new KnnByteVectorField(VECTOR_FIELD, byteVectorBuffer, fieldType)); + } + case FLOAT32 -> { + in.next(floatVectorBuffer); + doc.add(new KnnFloatVectorField(VECTOR_FIELD, floatVectorBuffer, fieldType)); + } + } + + if ((id + 1) % 25000 == 0) { + logger.debug("Done indexing " + (id + 1) + " documents."); + } + doc.add(new StoredField(ID_FIELD, id)); + iw.addDocument(doc); + } + } + } + + static class VectorReader { + final float[] target; + final ByteBuffer bytes; + final FileChannel input; + long position; + + static VectorReader create(FileChannel input, int dim, VectorEncoding vectorEncoding) throws IOException { + int bufferSize = dim * vectorEncoding.byteSize; + if (input.size() % ((long) dim * vectorEncoding.byteSize) != 0) { + throw new IllegalArgumentException( + "vectors file \"" + input + "\" does not contain a whole number of vectors? size=" + input.size() + ); + } + return new VectorReader(input, dim, bufferSize); + } + + VectorReader(FileChannel input, int dim, int bufferSize) throws IOException { + this.bytes = ByteBuffer.wrap(new byte[bufferSize]).order(ByteOrder.LITTLE_ENDIAN); + this.input = input; + this.target = new float[dim]; + reset(); + } + + void reset() throws IOException { + position = 0; + input.position(position); + } + + private void readNext() throws IOException { + int bytesRead = Channels.readFromFileChannel(this.input, position, bytes); + if (bytesRead < bytes.capacity()) { + position = 0; + bytes.position(0); + // wrap around back to the start of the file if we hit the end: + logger.warn("VectorReader hit EOF when reading " + this.input + "; now wrapping around to start of file again"); + this.input.position(position); + bytesRead = Channels.readFromFileChannel(this.input, position, bytes); + if (bytesRead < bytes.capacity()) { + throw new IllegalStateException( + "vector file " + input + " doesn't even have enough bytes for a single vector? got bytesRead=" + bytesRead + ); + } + } + position += bytesRead; + bytes.position(0); + } + + synchronized void next(float[] dest) throws IOException { + readNext(); + bytes.asFloatBuffer().get(dest); + } + + synchronized void next(byte[] dest) throws IOException { + readNext(); + bytes.get(dest); + } + } +} diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java new file mode 100644 index 0000000000000..b0738a6ea5bfb --- /dev/null +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java @@ -0,0 +1,488 @@ +/* + * @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. + * a copy and modification from Lucene util + * Modifications copyright (C) 2025 Elasticsearch B.V. + */ + +package org.elasticsearch.test.knn; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.StoredFields; +import org.apache.lucene.index.VectorEncoding; +import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.queries.function.FunctionQuery; +import org.apache.lucene.queries.function.valuesource.ByteKnnVectorFieldSource; +import org.apache.lucene.queries.function.valuesource.ByteVectorSimilarityFunction; +import org.apache.lucene.queries.function.valuesource.ConstKnnByteVectorValueSource; +import org.apache.lucene.queries.function.valuesource.ConstKnnFloatValueSource; +import org.apache.lucene.queries.function.valuesource.FloatKnnVectorFieldSource; +import org.apache.lucene.queries.function.valuesource.FloatVectorSimilarityFunction; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.TotalHits; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.store.MMapDirectory; +import org.elasticsearch.core.PathUtils; +import org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper; +import org.elasticsearch.search.profile.query.QueryProfiler; +import org.elasticsearch.search.vectors.ESKnnByteVectorQuery; +import org.elasticsearch.search.vectors.ESKnnFloatVectorQuery; +import org.elasticsearch.search.vectors.IVFKnnFloatVectorQuery; +import org.elasticsearch.search.vectors.QueryProfilerProvider; +import org.elasticsearch.search.vectors.RescoreKnnVectorQuery; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; + +import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS; +import static org.elasticsearch.test.knn.KnnIndexTester.logger; +import static org.elasticsearch.test.knn.KnnIndexer.ID_FIELD; +import static org.elasticsearch.test.knn.KnnIndexer.VECTOR_FIELD; + +class KnnSearcher { + + private final Path docPath; + private final Path indexPath; + private final Path queryPath; + private final int numDocs; + private final int numQueryVectors; + private final long randomSeed = 42; + private final float selectivity = 1f; + private final int topK; + private final int efSearch; + private final int nProbe; + private final KnnIndexTester.IndexType indexType; + private final int dim; + private final VectorSimilarityFunction similarityFunction; + private final VectorEncoding vectorEncoding; + private final float overSamplingFactor; + private final int searchThreads; + + KnnSearcher(Path indexPath, CmdLineArgs cmdLineArgs) { + this.docPath = cmdLineArgs.docVectors(); + this.indexPath = indexPath; + this.queryPath = cmdLineArgs.queryVectors(); + this.numDocs = cmdLineArgs.numDocs(); + this.numQueryVectors = cmdLineArgs.numQueries(); + this.topK = cmdLineArgs.k(); + this.dim = cmdLineArgs.dimensions(); + this.similarityFunction = cmdLineArgs.vectorSpace(); + this.vectorEncoding = cmdLineArgs.vectorEncoding(); + this.overSamplingFactor = cmdLineArgs.overSamplingFactor(); + if (numQueryVectors <= 0) { + throw new IllegalArgumentException("numQueryVectors must be > 0"); + } + this.efSearch = cmdLineArgs.numCandidates(); + this.nProbe = cmdLineArgs.nProbe(); + this.indexType = cmdLineArgs.indexType(); + this.searchThreads = cmdLineArgs.searchThreads(); + } + + void runSearch(KnnIndexTester.Results finalResults) throws IOException { + TopDocs[] results = new TopDocs[numQueryVectors]; + int[][] resultIds = new int[numQueryVectors][]; + long elapsed, totalCpuTimeMS, totalVisited = 0; + try ( + FileChannel input = FileChannel.open(queryPath); + ExecutorService executorService = Executors.newFixedThreadPool(searchThreads, r -> new Thread(r, "KnnSearcher-Thread")) + ) { + long queryPathSizeInBytes = input.size(); + logger.info( + "queryPath size: " + + queryPathSizeInBytes + + " bytes, assuming vector count is " + + (queryPathSizeInBytes / ((long) dim * vectorEncoding.byteSize)) + ); + KnnIndexer.VectorReader targetReader = KnnIndexer.VectorReader.create(input, dim, vectorEncoding); + long startNS; + try (MMapDirectory dir = new MMapDirectory(indexPath)) { + try (DirectoryReader reader = DirectoryReader.open(dir)) { + IndexSearcher searcher = searchThreads > 1 ? new IndexSearcher(reader, executorService) : new IndexSearcher(reader); + byte[] targetBytes = new byte[dim]; + float[] target = new float[dim]; + // warm up + for (int i = 0; i < numQueryVectors; i++) { + if (vectorEncoding.equals(VectorEncoding.BYTE)) { + targetReader.next(targetBytes); + doVectorQuery(targetBytes, searcher); + } else { + targetReader.next(target); + doVectorQuery(target, searcher); + } + } + targetReader.reset(); + startNS = System.nanoTime(); + KnnIndexTester.ThreadDetails startThreadDetails = new KnnIndexTester.ThreadDetails(); + for (int i = 0; i < numQueryVectors; i++) { + if (vectorEncoding.equals(VectorEncoding.BYTE)) { + targetReader.next(targetBytes); + results[i] = doVectorQuery(targetBytes, searcher); + } else { + targetReader.next(target); + results[i] = doVectorQuery(target, searcher); + } + } + KnnIndexTester.ThreadDetails endThreadDetails = new KnnIndexTester.ThreadDetails(); + elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNS); + long startCPUTimeNS = 0; + long endCPUTimeNS = 0; + for (int i = 0; i < startThreadDetails.threadInfos.length; i++) { + if (startThreadDetails.threadInfos[i].getThreadName().startsWith("KnnSearcher-Thread")) { + startCPUTimeNS += startThreadDetails.cpuTimesNS[i]; + } + } + + for (int i = 0; i < endThreadDetails.threadInfos.length; i++) { + if (endThreadDetails.threadInfos[i].getThreadName().startsWith("KnnSearcher-Thread")) { + endCPUTimeNS += endThreadDetails.cpuTimesNS[i]; + } + } + totalCpuTimeMS = TimeUnit.NANOSECONDS.toMillis(endCPUTimeNS - startCPUTimeNS); + + // Fetch, validate and write result document ids. + StoredFields storedFields = reader.storedFields(); + for (int i = 0; i < numQueryVectors; i++) { + totalVisited += results[i].totalHits.value(); + resultIds[i] = getResultIds(results[i], storedFields); + } + logger.info( + "completed %d searches in %d ms: %d QPS CPU time=%dms", + numQueryVectors, + elapsed, + (1000L * numQueryVectors) / elapsed, + totalCpuTimeMS + ); + } + } + } + logger.info("checking results"); + int[][] nn = getOrCalculateExactNN(); + finalResults.avgRecall = checkResults(resultIds, nn, topK); + finalResults.qps = (1000f * numQueryVectors) / elapsed; + finalResults.avgLatency = (float) elapsed / numQueryVectors; + finalResults.averageVisited = (double) totalVisited / numQueryVectors; + finalResults.netCpuTimeMS = (double) totalCpuTimeMS / numQueryVectors; + finalResults.avgCpuCount = (double) totalCpuTimeMS / elapsed; + } + + private int[][] getOrCalculateExactNN() throws IOException { + // look in working directory for cached nn file + String hash = Integer.toString( + Objects.hash( + docPath, + indexPath, + queryPath, + numDocs, + numQueryVectors, + topK, + similarityFunction.ordinal(), + selectivity, + randomSeed + ), + 36 + ); + String nnFileName = "nn-" + hash + ".bin"; + Path nnPath = PathUtils.get("target/" + nnFileName); + if (Files.exists(nnPath) && isNewer(nnPath, docPath, indexPath, queryPath)) { + logger.info("read pre-cached exact match vectors from cache file \"" + nnPath + "\""); + return readExactNN(nnPath); + } else { + logger.info("computing brute-force exact KNN matches for " + numQueryVectors + " query vectors from \"" + queryPath + "\""); + long startNS = System.nanoTime(); + // TODO: enable computing NN from high precision vectors when + // checking low-precision recall + int[][] nn; + if (vectorEncoding.equals(VectorEncoding.BYTE)) { + nn = computeExactNNByte(queryPath); + } else { + nn = computeExactNN(queryPath); + } + writeExactNN(nn, nnPath); + long elapsedMS = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNS); // ns -> ms + logger.info("computed " + numQueryVectors + " exact matches in " + elapsedMS + " ms"); + return nn; + } + } + + private boolean isNewer(Path path, Path... others) throws IOException { + FileTime modified = Files.getLastModifiedTime(path); + for (Path other : others) { + if (Files.getLastModifiedTime(other).compareTo(modified) >= 0) { + return false; + } + } + return true; + } + + TopDocs doVectorQuery(byte[] vector, IndexSearcher searcher) throws IOException { + Query knnQuery; + if (overSamplingFactor > 1f) { + throw new IllegalArgumentException("oversampling factor > 1 is not supported for byte vectors"); + } + if (indexType == KnnIndexTester.IndexType.IVF) { + throw new IllegalArgumentException("IVF index type does not support byte vectors"); + } else { + knnQuery = new ESKnnByteVectorQuery( + VECTOR_FIELD, + vector, + topK, + efSearch, + null, + DenseVectorFieldMapper.FilterHeuristic.ACORN.getKnnSearchStrategy() + ); + } + QueryProfiler profiler = new QueryProfiler(); + TopDocs docs = searcher.search(knnQuery, this.topK); + QueryProfilerProvider queryProfilerProvider = (QueryProfilerProvider) knnQuery; + queryProfilerProvider.profile(profiler); + return new TopDocs(new TotalHits(profiler.getVectorOpsCount(), docs.totalHits.relation()), docs.scoreDocs); + } + + TopDocs doVectorQuery(float[] vector, IndexSearcher searcher) throws IOException { + Query knnQuery; + int topK = this.topK; + int efSearch = this.efSearch; + if (overSamplingFactor > 1f) { + // oversample the topK results to get more candidates for the final result + topK = (int) Math.ceil(topK * overSamplingFactor); + efSearch = Math.max(topK, efSearch); + } + if (indexType == KnnIndexTester.IndexType.IVF) { + knnQuery = new IVFKnnFloatVectorQuery(VECTOR_FIELD, vector, topK, efSearch, null, nProbe); + } else { + knnQuery = new ESKnnFloatVectorQuery( + VECTOR_FIELD, + vector, + topK, + efSearch, + null, + DenseVectorFieldMapper.FilterHeuristic.ACORN.getKnnSearchStrategy() + ); + } + if (overSamplingFactor > 1f) { + // oversample the topK results to get more candidates for the final result + knnQuery = new RescoreKnnVectorQuery(VECTOR_FIELD, vector, similarityFunction, this.topK, knnQuery); + } + QueryProfiler profiler = new QueryProfiler(); + TopDocs docs = searcher.search(knnQuery, this.topK); + QueryProfilerProvider queryProfilerProvider = (QueryProfilerProvider) knnQuery; + queryProfilerProvider.profile(profiler); + return new TopDocs(new TotalHits(profiler.getVectorOpsCount(), docs.totalHits.relation()), docs.scoreDocs); + } + + private static float checkResults(int[][] results, int[][] nn, int topK) { + int totalMatches = 0; + int totalResults = results.length * topK; + for (int i = 0; i < results.length; i++) { + totalMatches += compareNN(nn[i], results[i], topK); + } + return totalMatches / (float) totalResults; + } + + private static int compareNN(int[] expected, int[] results, int topK) { + int matched = 0; + Set expectedSet = new HashSet<>(); + Set alreadySeen = new HashSet<>(); + for (int i = 0; i < topK; i++) { + expectedSet.add(expected[i]); + } + for (int docId : results) { + if (alreadySeen.add(docId) == false) { + throw new IllegalStateException("duplicate docId=" + docId); + } + if (expectedSet.contains(docId)) { + ++matched; + } + } + return matched; + } + + private int[][] readExactNN(Path nnPath) throws IOException { + int[][] result = new int[numQueryVectors][]; + try (FileChannel in = FileChannel.open(nnPath)) { + IntBuffer intBuffer = in.map(FileChannel.MapMode.READ_ONLY, 0, (long) numQueryVectors * topK * Integer.BYTES) + .order(ByteOrder.LITTLE_ENDIAN) + .asIntBuffer(); + for (int i = 0; i < numQueryVectors; i++) { + result[i] = new int[topK]; + intBuffer.get(result[i]); + } + } + return result; + } + + private void writeExactNN(int[][] nn, Path nnPath) throws IOException { + logger.info("writing true nearest neighbors to cache file \"" + nnPath + "\""); + ByteBuffer tmp = ByteBuffer.allocate(nn[0].length * Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); + try (OutputStream out = Files.newOutputStream(nnPath)) { + for (int i = 0; i < numQueryVectors; i++) { + tmp.asIntBuffer().put(nn[i]); + out.write(tmp.array()); + } + } + } + + private int[][] computeExactNN(Path queryPath) throws IOException { + int[][] result = new int[numQueryVectors][]; + try (Directory dir = FSDirectory.open(indexPath); DirectoryReader reader = DirectoryReader.open(dir)) { + List> tasks = new ArrayList<>(); + try (FileChannel qIn = FileChannel.open(queryPath)) { + KnnIndexer.VectorReader queryReader = KnnIndexer.VectorReader.create(qIn, dim, VectorEncoding.FLOAT32); + for (int i = 0; i < numQueryVectors; i++) { + float[] queryVector = new float[dim]; + queryReader.next(queryVector); + tasks.add(new ComputeNNFloatTask(i, topK, queryVector, result, reader, similarityFunction)); + } + ForkJoinPool.commonPool().invokeAll(tasks); + } + return result; + } + } + + private int[][] computeExactNNByte(Path queryPath) throws IOException { + int[][] result = new int[numQueryVectors][]; + try (Directory dir = FSDirectory.open(indexPath); DirectoryReader reader = DirectoryReader.open(dir)) { + List> tasks = new ArrayList<>(); + try (FileChannel qIn = FileChannel.open(queryPath)) { + KnnIndexer.VectorReader queryReader = KnnIndexer.VectorReader.create(qIn, dim, VectorEncoding.BYTE); + for (int i = 0; i < numQueryVectors; i++) { + byte[] queryVector = new byte[dim]; + queryReader.next(queryVector); + tasks.add(new ComputeNNByteTask(i, queryVector, result, reader, similarityFunction)); + } + ForkJoinPool.commonPool().invokeAll(tasks); + } + return result; + } + } + + static class ComputeNNFloatTask implements Callable { + + private final int queryOrd; + private final float[] query; + private final int[][] result; + private final IndexReader reader; + private final VectorSimilarityFunction similarityFunction; + private final int topK; + + ComputeNNFloatTask( + int queryOrd, + int topK, + float[] query, + int[][] result, + IndexReader reader, + VectorSimilarityFunction similarityFunction + ) { + this.queryOrd = queryOrd; + this.query = query; + this.result = result; + this.reader = reader; + this.similarityFunction = similarityFunction; + this.topK = topK; + } + + @Override + public Void call() { + IndexSearcher searcher = new IndexSearcher(reader); + try { + var queryVector = new ConstKnnFloatValueSource(query); + var docVectors = new FloatKnnVectorFieldSource(VECTOR_FIELD); + Query query = new FunctionQuery(new FloatVectorSimilarityFunction(similarityFunction, queryVector, docVectors)); + var topDocs = searcher.search(query, topK); + result[queryOrd] = getResultIds(topDocs, reader.storedFields()); + if ((queryOrd + 1) % 10 == 0) { + logger.info(" exact knn scored " + (queryOrd + 1)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + } + } + + static class ComputeNNByteTask implements Callable { + + private final int queryOrd; + private final byte[] query; + private final int[][] result; + private final IndexReader reader; + private final VectorSimilarityFunction similarityFunction; + + ComputeNNByteTask(int queryOrd, byte[] query, int[][] result, IndexReader reader, VectorSimilarityFunction similarityFunction) { + this.queryOrd = queryOrd; + this.query = query; + this.result = result; + this.reader = reader; + this.similarityFunction = similarityFunction; + } + + @Override + public Void call() { + IndexSearcher searcher = new IndexSearcher(reader); + int topK = result[0].length; + try { + var queryVector = new ConstKnnByteVectorValueSource(query); + var docVectors = new ByteKnnVectorFieldSource(VECTOR_FIELD); + Query query = new FunctionQuery(new ByteVectorSimilarityFunction(similarityFunction, queryVector, docVectors)); + var topDocs = searcher.search(query, topK); + result[queryOrd] = getResultIds(topDocs, reader.storedFields()); + if ((queryOrd + 1) % 10 == 0) { + logger.info(" exact knn scored " + (queryOrd + 1)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + } + } + + static int[] getResultIds(TopDocs topDocs, StoredFields storedFields) throws IOException { + int[] resultIds = new int[topDocs.scoreDocs.length]; + int i = 0; + for (ScoreDoc doc : topDocs.scoreDocs) { + if (doc.doc != NO_MORE_DOCS) { + // there is a bug somewhere that can result in doc=NO_MORE_DOCS! I think it happens + // in some degenerate case (like input query has NaN in it?) that causes no results to + // be returned from HNSW search? + resultIds[i++] = Integer.parseInt(storedFields.document(doc.doc).get(ID_FIELD)); + } + } + return resultIds; + } + +} diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index c0e180c543a57..cd418d21e05c8 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -479,4 +479,6 @@ exports org.elasticsearch.lucene.util.automaton; exports org.elasticsearch.index.codec.perfield; exports org.elasticsearch.lucene.search; + exports org.elasticsearch.index.codec.vectors to org.elasticsearch.test.knn; + exports org.elasticsearch.index.codec.vectors.es818 to org.elasticsearch.test.knn; } diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java index 12726836719be..f2145b463ad9a 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java @@ -51,6 +51,7 @@ public abstract class IVFVectorsReader extends KnnVectorsReader { protected final IntObjectHashMap fields; private final FlatVectorsReader rawVectorsReader; + @SuppressWarnings("this-escape") protected IVFVectorsReader(SegmentReadState state, FlatVectorsReader rawVectorsReader) throws IOException { this.state = state; this.fieldInfos = state.fieldInfos; diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java index 4e9c4ee47e3f6..d6188703881a4 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java @@ -55,6 +55,7 @@ public abstract class IVFVectorsWriter extends KnnVectorsWriter { private final FlatVectorsWriter rawVectorDelegate; private final SegmentWriteState segmentWriteState; + @SuppressWarnings("this-escape") protected IVFVectorsWriter(SegmentWriteState state, FlatVectorsWriter rawVectorDelegate) throws IOException { this.segmentWriteState = state; this.rawVectorDelegate = rawVectorDelegate; diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/es816/ES816BinaryQuantizedVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/es816/ES816BinaryQuantizedVectorsReader.java index f809fd81bbd52..c86dec80c7bac 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/es816/ES816BinaryQuantizedVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/es816/ES816BinaryQuantizedVectorsReader.java @@ -69,6 +69,7 @@ public class ES816BinaryQuantizedVectorsReader extends FlatVectorsReader impleme private final FlatVectorsReader rawVectorsReader; private final ES816BinaryFlatVectorsScorer vectorScorer; + @SuppressWarnings("this-escape") ES816BinaryQuantizedVectorsReader( SegmentReadState state, FlatVectorsReader rawVectorsReader, diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsReader.java index d1c107ebe15a9..bdf1e3c925b2d 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsReader.java @@ -65,6 +65,7 @@ public class DirectIOLucene99FlatVectorsReader extends FlatVectorsReader impleme private final IndexInput vectorData; private final FieldInfos fieldInfos; + @SuppressWarnings("this-escape") public DirectIOLucene99FlatVectorsReader(SegmentReadState state, FlatVectorsScorer scorer) throws IOException { super(scorer); int versionMeta = readMetadata(state); diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsReader.java index ac707d155ea3f..333f47a284daf 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsReader.java @@ -70,6 +70,7 @@ public class ES818BinaryQuantizedVectorsReader extends FlatVectorsReader impleme private final FlatVectorsReader rawVectorsReader; private final ES818BinaryFlatVectorsScorer vectorScorer; + @SuppressWarnings("this-escape") ES818BinaryQuantizedVectorsReader( SegmentReadState state, FlatVectorsReader rawVectorsReader, diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsWriter.java index 7cfa755c26107..a4983e234c8db 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsWriter.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsWriter.java @@ -84,6 +84,7 @@ public class ES818BinaryQuantizedVectorsWriter extends FlatVectorsWriter { * * @param vectorsScorer the scorer to use for scoring vectors */ + @SuppressWarnings("this-escape") protected ES818BinaryQuantizedVectorsWriter( ES818BinaryFlatVectorsScorer vectorsScorer, FlatVectorsWriter rawVectorDelegate, diff --git a/settings.gradle b/settings.gradle index 61487d204828f..6bca35f40bb26 100644 --- a/settings.gradle +++ b/settings.gradle @@ -171,3 +171,5 @@ if (extraProjects.exists()) { addSubProjects('', extraProjectDir) } } + +include 'qa:vector' \ No newline at end of file diff --git a/test/external-modules/build.gradle b/test/external-modules/build.gradle index dfdc47d9f5beb..7b317f27f0b85 100644 --- a/test/external-modules/build.gradle +++ b/test/external-modules/build.gradle @@ -8,11 +8,11 @@ */ subprojects { - apply plugin: 'elasticsearch.base-internal-es-plugin' + apply plugin: 'elasticsearch.base-internal-es-plugin' - esplugin { - name = it.name - licenseFile = layout.settingsDirectory.file('licenses/AGPL-3.0+SSPL-1.0+ELASTIC-LICENSE-2.0.txt').asFile - noticeFile = layout.settingsDirectory.file('NOTICE.txt').asFile - } + esplugin { + name = it.name + licenseFile = layout.settingsDirectory.file('licenses/AGPL-3.0+SSPL-1.0+ELASTIC-LICENSE-2.0.txt').asFile + noticeFile = layout.settingsDirectory.file('NOTICE.txt').asFile + } } From df3ef0da2683b9976aa64097374babdf86963eec Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Fri, 6 Jun 2025 18:08:46 +0200 Subject: [PATCH 009/102] ES|QL: refactor generative tests (#129028) --- .../rest/generative/EsqlQueryGenerator.java | 449 +++--------------- .../rest/generative/GenerativeRestTest.java | 70 ++- .../esql/qa/rest/generative/README.asciidoc | 43 ++ .../generative/command/CommandGenerator.java | 142 ++++++ .../command/pipe/DissectGenerator.java | 78 +++ .../command/pipe/DropGenerator.java | 91 ++++ .../command/pipe/EnrichGenerator.java | 61 +++ .../command/pipe/EvalGenerator.java | 96 ++++ .../command/pipe/GrokGenerator.java | 75 +++ .../command/pipe/KeepGenerator.java | 82 ++++ .../command/pipe/LimitGenerator.java | 57 +++ .../command/pipe/LookupJoinGenerator.java | 63 +++ .../command/pipe/MvExpandGenerator.java | 51 ++ .../command/pipe/RenameGenerator.java | 103 ++++ .../command/pipe/SortGenerator.java | 60 +++ .../command/pipe/StatsGenerator.java | 80 ++++ .../command/pipe/WhereGenerator.java | 63 +++ .../command/source/FromGenerator.java | 54 +++ 18 files changed, 1333 insertions(+), 385 deletions(-) create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java index 31fddae7c6859..46f48963c820c 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/EsqlQueryGenerator.java @@ -8,12 +8,23 @@ package org.elasticsearch.xpack.esql.qa.rest.generative; import org.elasticsearch.xpack.esql.CsvTestsDataLoader; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.DissectGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.DropGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.EnrichGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.EvalGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.GrokGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.KeepGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LimitGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.LookupJoinGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.MvExpandGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.RenameGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.SortGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.StatsGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe.WhereGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.source.FromGenerator; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -27,85 +38,41 @@ public class EsqlQueryGenerator { public record Column(String name, String type) {} - public record QueryExecuted(String query, int depth, List outputSchema, Exception exception) {} - - public static String sourceCommand(List availabeIndices) { - return switch (randomIntBetween(0, 1)) { - case 0 -> from(availabeIndices); - // case 1 -> metaFunctions(); - default -> from(availabeIndices); - // TODO re-enable ROW. - // now it crashes nodes in some cases: exiting java.lang.AssertionError: estimated row size [0] wasn't set - // default -> row(); - }; - - } + public record QueryExecuted(String query, int depth, List outputSchema, List> result, Exception exception) {} /** - * @param previousOutput a list of fieldName+type - * @param policies - * @return a new command that can process it as input + * These are commands that are at the beginning of the query, eg. FROM */ - public static String pipeCommand( - List previousOutput, - List policies, - List lookupIndices - ) { - return switch (randomIntBetween(0, 12)) { - case 0 -> dissect(previousOutput); - case 1 -> drop(previousOutput); - case 2 -> enrich(previousOutput, policies); - case 3 -> eval(previousOutput); - case 4 -> grok(previousOutput); - case 5 -> keep(previousOutput); - case 6 -> limit(); - case 7 -> mvExpand(previousOutput); - case 8 -> rename(previousOutput); - case 9 -> sort(previousOutput); - case 10 -> stats(previousOutput); - case 11 -> join(previousOutput, lookupIndices); - default -> where(previousOutput); - }; - } - - private static String join(List previousOutput, List lookupIndices) { - - GenerativeRestTest.LookupIdx lookupIdx = randomFrom(lookupIndices); - String lookupIdxName = lookupIdx.idxName(); - String idxKey = lookupIdx.key(); - String keyType = lookupIdx.keyType(); - - var candidateKeys = previousOutput.stream().filter(x -> x.type.equals(keyType)).toList(); - if (candidateKeys.isEmpty()) { - return ""; - } - Column key = randomFrom(candidateKeys); - return "| rename " + key.name + " as " + idxKey + " | lookup join " + lookupIdxName + " on " + idxKey; - } - - private static String where(List previousOutput) { - // TODO more complex conditions - StringBuilder result = new StringBuilder(" | where "); - int nConditions = randomIntBetween(1, 5); - for (int i = 0; i < nConditions; i++) { - String exp = booleanExpression(previousOutput); - if (exp == null) { - // cannot generate expressions, just skip - return ""; - } - if (i > 0) { - result.append(randomBoolean() ? " AND " : " OR "); - } - if (randomBoolean()) { - result.append(" NOT "); - } - result.append(exp); - } - - return result.toString(); - } + static List SOURCE_COMMANDS = List.of(FromGenerator.INSTANCE); - private static String booleanExpression(List previousOutput) { + /** + * These are downstream commands, ie. that cannot appear as the first command in a query + */ + static List PIPE_COMMANDS = List.of( + DissectGenerator.INSTANCE, + DropGenerator.INSTANCE, + EnrichGenerator.INSTANCE, + EvalGenerator.INSTANCE, + GrokGenerator.INSTANCE, + KeepGenerator.INSTANCE, + LimitGenerator.INSTANCE, + LookupJoinGenerator.INSTANCE, + MvExpandGenerator.INSTANCE, + RenameGenerator.INSTANCE, + SortGenerator.INSTANCE, + StatsGenerator.INSTANCE, + WhereGenerator.INSTANCE + ); + + public static CommandGenerator sourceCommand() { + return randomFrom(SOURCE_COMMANDS); + } + + public static CommandGenerator randomPipeCommandGenerator() { + return randomFrom(PIPE_COMMANDS); + } + + public static String booleanExpression(List previousOutput) { // TODO LIKE, RLIKE, functions etc. return switch (randomIntBetween(0, 3)) { case 0 -> { @@ -120,7 +87,7 @@ private static String booleanExpression(List previousOutput) { }; } - private static String mathCompareOperator() { + public static String mathCompareOperator() { return switch (randomIntBetween(0, 5)) { case 0 -> "=="; case 1 -> ">"; @@ -131,105 +98,12 @@ private static String mathCompareOperator() { }; } - private static String enrich(List previousOutput, List policies) { - String field = randomKeywordField(previousOutput); - if (field == null || policies.isEmpty()) { - return ""; - } - - // TODO add WITH - return " | enrich " + randomFrom(policiesOnKeyword(policies)).policyName() + " on " + field; - } - - private static List policiesOnKeyword(List policies) { + public static List policiesOnKeyword(List policies) { // TODO make it smarter and extend it to other types return policies.stream().filter(x -> Set.of("languages_policy").contains(x.policyName())).toList(); } - private static String grok(List previousOutput) { - String field = randomStringField(previousOutput); - if (field == null) { - return "";// no strings to grok, just skip - } - StringBuilder result = new StringBuilder(" | grok "); - result.append(field); - result.append(" \""); - for (int i = 0; i < randomIntBetween(1, 3); i++) { - if (i > 0) { - result.append(" "); - } - result.append("%{WORD:"); - if (randomBoolean()) { - result.append(randomIdentifier()); - } else { - String fieldName = randomRawName(previousOutput); - if (fieldName == null) { - fieldName = randomIdentifier(); - } - result.append(fieldName); - } - result.append("}"); - } - result.append("\""); - return result.toString(); - } - - private static String dissect(List previousOutput) { - String field = randomStringField(previousOutput); - if (field == null) { - return "";// no strings to dissect, just skip - } - StringBuilder result = new StringBuilder(" | dissect "); - result.append(field); - result.append(" \""); - for (int i = 0; i < randomIntBetween(1, 3); i++) { - if (i > 0) { - result.append(" "); - } - result.append("%{"); - if (randomBoolean()) { - result.append(randomIdentifier()); - } else { - String fieldName = randomRawName(previousOutput); - if (fieldName == null) { - fieldName = randomIdentifier(); - } - result.append(fieldName); - } - result.append("}"); - } - result.append("\""); - return result.toString(); - } - - private static String keep(List previousOutput) { - int n = randomIntBetween(1, previousOutput.size()); - Set proj = new HashSet<>(); - for (int i = 0; i < n; i++) { - if (randomIntBetween(0, 100) < 5) { - proj.add("*"); - } else { - String name = randomName(previousOutput); - if (name == null) { - continue; - } - if (name.length() > 1 && name.startsWith("`") == false && randomIntBetween(0, 100) < 10) { - if (randomBoolean()) { - name = name.substring(0, randomIntBetween(1, name.length() - 1)) + "*"; - } else { - name = "*" + name.substring(randomIntBetween(1, name.length() - 1)); - } - } - proj.add(name); - } - } - if (proj.isEmpty()) { - return ""; - } - return " | keep " + proj.stream().collect(Collectors.joining(", ")); - } - - private static String randomName(List previousOutput) { + public static String randomName(List previousOutput) { String result = randomRawName(previousOutput); if (result == null) { return null; @@ -244,7 +118,7 @@ private static String randomName(List previousOutput) { * Returns a field name from a list of columns. * Could be null if none of the fields can be considered */ - private static String randomRawName(List previousOutput) { + public static String randomRawName(List previousOutput) { var list = previousOutput.stream().filter(EsqlQueryGenerator::fieldCanBeUsed).toList(); if (list.isEmpty()) { return null; @@ -257,7 +131,7 @@ private static String randomRawName(List previousOutput) { * Returns a field that can be used for grouping. * Can return null */ - private static String randomGroupableName(List previousOutput) { + public static String randomGroupableName(List previousOutput) { var candidates = previousOutput.stream().filter(EsqlQueryGenerator::groupable).filter(EsqlQueryGenerator::fieldCanBeUsed).toList(); if (candidates.isEmpty()) { return null; @@ -265,7 +139,7 @@ private static String randomGroupableName(List previousOutput) { return randomFrom(candidates).name(); } - private static boolean groupable(Column col) { + public static boolean groupable(Column col) { return col.type.equals("keyword") || col.type.equals("text") || col.type.equals("long") @@ -278,7 +152,7 @@ private static boolean groupable(Column col) { * returns a field that can be sorted. * Null if no fields are sortable. */ - private static String randomSortableName(List previousOutput) { + public static String randomSortableName(List previousOutput) { var candidates = previousOutput.stream().filter(EsqlQueryGenerator::sortable).filter(EsqlQueryGenerator::fieldCanBeUsed).toList(); if (candidates.isEmpty()) { return null; @@ -286,7 +160,7 @@ private static String randomSortableName(List previousOutput) { return randomFrom(candidates).name(); } - private static boolean sortable(Column col) { + public static boolean sortable(Column col) { return col.type.equals("keyword") || col.type.equals("text") || col.type.equals("long") @@ -295,170 +169,7 @@ private static boolean sortable(Column col) { || col.type.equals("version"); } - private static String rename(List previousOutput) { - int n = randomIntBetween(1, Math.min(3, previousOutput.size())); - List proj = new ArrayList<>(); - - Map nameToType = new HashMap<>(); - for (Column column : previousOutput) { - nameToType.put(column.name, column.type); - } - List names = new ArrayList<>( - previousOutput.stream().filter(EsqlQueryGenerator::fieldCanBeUsed).map(Column::name).collect(Collectors.toList()) - ); - if (names.isEmpty()) { - return ""; - } - for (int i = 0; i < n; i++) { - if (names.isEmpty()) { - break; - } - var name = randomFrom(names); - if (nameToType.get(name).endsWith("_range")) { - // ranges are not fully supported yet - continue; - } - names.remove(name); - - String newName; - if (names.isEmpty() || randomBoolean()) { - newName = randomIdentifier(); - names.add(newName); - } else { - newName = names.get(randomIntBetween(0, names.size() - 1)); - } - nameToType.put(newName, nameToType.get(name)); - if (randomBoolean() && name.startsWith("`") == false) { - name = "`" + name + "`"; - } - if (randomBoolean() && newName.startsWith("`") == false) { - newName = "`" + newName + "`"; - } - proj.add(name + " AS " + newName); - } - if (proj.isEmpty()) { - return ""; - } - return " | rename " + proj.stream().collect(Collectors.joining(", ")); - } - - private static String drop(List previousOutput) { - if (previousOutput.size() < 2) { - return ""; // don't drop all of them, just do nothing - } - int n = randomIntBetween(1, previousOutput.size() - 1); - Set proj = new HashSet<>(); - for (int i = 0; i < n; i++) { - String name = randomRawName(previousOutput); - if (name == null) { - continue; - } - if (name.length() > 1 && name.startsWith("`") == false && randomIntBetween(0, 100) < 10) { - if (randomBoolean()) { - name = name.substring(0, randomIntBetween(1, name.length() - 1)) + "*"; - } else { - name = "*" + name.substring(randomIntBetween(1, name.length() - 1)); - } - } else if (name.startsWith("`") == false && (randomBoolean() || name.isEmpty())) { - name = "`" + name + "`"; - } - proj.add(name); - } - if (proj.isEmpty()) { - return ""; - } - return " | drop " + proj.stream().collect(Collectors.joining(", ")); - } - - private static String sort(List previousOutput) { - int n = randomIntBetween(1, previousOutput.size()); - Set proj = new HashSet<>(); - for (int i = 0; i < n; i++) { - String col = randomSortableName(previousOutput); - if (col == null) { - return "";// no sortable columns - } - proj.add(col); - } - return " | sort " - + proj.stream() - .map(x -> x + randomFrom("", " ASC", " DESC") + randomFrom("", " NULLS FIRST", " NULLS LAST")) - .collect(Collectors.joining(", ")); - } - - private static String mvExpand(List previousOutput) { - String toExpand = randomName(previousOutput); - if (toExpand == null) { - return ""; // no columns to expand - } - return " | mv_expand " + toExpand; - } - - private static String eval(List previousOutput) { - StringBuilder cmd = new StringBuilder(" | eval "); - int nFields = randomIntBetween(1, 10); - // TODO pass newly created fields to next expressions - for (int i = 0; i < nFields; i++) { - String name; - if (randomBoolean()) { - name = randomIdentifier(); - } else { - name = randomName(previousOutput); - if (name == null) { - name = randomIdentifier(); - } - } - String expression = expression(previousOutput); - if (i > 0) { - cmd.append(","); - } - cmd.append(" "); - cmd.append(name); - cmd.append(" = "); - cmd.append(expression); - } - return cmd.toString(); - } - - private static String stats(List previousOutput) { - List nonNull = previousOutput.stream() - .filter(EsqlQueryGenerator::fieldCanBeUsed) - .filter(x -> x.type().equals("null") == false) - .collect(Collectors.toList()); - if (nonNull.isEmpty()) { - return ""; // cannot do any stats, just skip - } - StringBuilder cmd = new StringBuilder(" | stats "); - int nStats = randomIntBetween(1, 5); - for (int i = 0; i < nStats; i++) { - String name; - if (randomBoolean()) { - name = randomIdentifier(); - } else { - name = randomName(previousOutput); - if (name == null) { - name = randomIdentifier(); - } - } - String expression = agg(nonNull); - if (i > 0) { - cmd.append(","); - } - cmd.append(" "); - cmd.append(name); - cmd.append(" = "); - cmd.append(expression); - } - if (randomBoolean()) { - var col = randomGroupableName(nonNull); - if (col != null) { - cmd.append(" by " + col); - } - } - return cmd.toString(); - } - - private static String agg(List previousOutput) { + public static String agg(List previousOutput) { String name = randomNumericOrDateField(previousOutput); if (name != null && randomBoolean()) { // numerics only @@ -480,23 +191,23 @@ private static String agg(List previousOutput) { }; } - private static String randomNumericOrDateField(List previousOutput) { + public static String randomNumericOrDateField(List previousOutput) { return randomName(previousOutput, Set.of("long", "integer", "double", "date")); } - private static String randomNumericField(List previousOutput) { + public static String randomNumericField(List previousOutput) { return randomName(previousOutput, Set.of("long", "integer", "double")); } - private static String randomStringField(List previousOutput) { + public static String randomStringField(List previousOutput) { return randomName(previousOutput, Set.of("text", "keyword")); } - private static String randomKeywordField(List previousOutput) { + public static String randomKeywordField(List previousOutput) { return randomName(previousOutput, Set.of("keyword")); } - private static String randomName(List cols, Set allowedTypes) { + public static String randomName(List cols, Set allowedTypes) { List items = cols.stream().filter(x -> allowedTypes.contains(x.type())).map(Column::name).collect(Collectors.toList()); if (items.size() == 0) { return null; @@ -504,37 +215,16 @@ private static String randomName(List cols, Set allowedTypes) { return items.get(randomIntBetween(0, items.size() - 1)); } - private static String expression(List previousOutput) { + public static String expression(List previousOutput) { // TODO improve!!! return constantExpression(); } - public static String limit() { - return " | limit " + randomIntBetween(0, 15000); - } - - private static String from(List availabeIndices) { - StringBuilder result = new StringBuilder("from "); - int items = randomIntBetween(1, 3); - for (int i = 0; i < items; i++) { - String pattern = indexPattern(availabeIndices.get(randomIntBetween(0, availabeIndices.size() - 1))); - if (i > 0) { - result.append(","); - } - result.append(pattern); - } - return result.toString(); - } - - private static String metaFunctions() { - return "meta functions"; - } - - private static String indexPattern(String indexName) { + public static String indexPattern(String indexName) { return randomBoolean() ? indexName : indexName.substring(0, randomIntBetween(0, indexName.length())) + "*"; } - private static String row() { + public static String row() { StringBuilder cmd = new StringBuilder("row "); int nFields = randomIntBetween(1, 10); for (int i = 0; i < nFields; i++) { @@ -551,7 +241,7 @@ private static String row() { return cmd.toString(); } - private static String constantExpression() { + public static String constantExpression() { // TODO not only simple values, but also foldable expressions return switch (randomIntBetween(0, 4)) { case 0 -> "" + randomIntBetween(Integer.MIN_VALUE, Integer.MAX_VALUE); @@ -563,13 +253,13 @@ private static String constantExpression() { } - private static String randomIdentifier() { + public static String randomIdentifier() { // Let's create identifiers that are long enough to avoid collisions with reserved keywords. // There could be a smarter way (introspection on the lexer class?), but probably it's not worth the effort return randomAlphaOfLength(randomIntBetween(8, 12)); } - private static boolean fieldCanBeUsed(Column field) { + public static boolean fieldCanBeUsed(Column field) { return ( // https://github.com/elastic/elasticsearch/issues/121741 field.name().equals("") @@ -577,4 +267,11 @@ private static boolean fieldCanBeUsed(Column field) { || field.name().equals("")) == false; } + public static String unquote(String colName) { + if (colName.startsWith("`") && colName.endsWith("`")) { + return colName.substring(1, colName.length() - 1); + } + return colName; + } + } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java index 8ae28477e03bb..88df38cc347cf 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeRestTest.java @@ -12,6 +12,7 @@ import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.xpack.esql.CsvTestsDataLoader; import org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; import org.junit.AfterClass; import org.junit.Before; @@ -47,11 +48,14 @@ public abstract class GenerativeRestTest extends ESRestTestCase { "Field '.*' shadowed by field at line .*", "evaluation of \\[.*\\] failed, treating result as null", // TODO investigate? - // Awaiting fixes + // Awaiting fixes for query failure "Unknown column \\[\\]", // https://github.com/elastic/elasticsearch/issues/121741, "Plan \\[ProjectExec\\[\\[.* optimized incorrectly due to missing references", // https://github.com/elastic/elasticsearch/issues/125866 "optimized incorrectly due to missing references", // https://github.com/elastic/elasticsearch/issues/116781 - "The incoming YAML document exceeds the limit:" // still to investigate, but it seems to be specific to the test framework + "The incoming YAML document exceeds the limit:", // still to investigate, but it seems to be specific to the test framework + + // Awaiting fixes for correctness + "Expecting the following columns \\[.*\\], got" // https://github.com/elastic/elasticsearch/issues/129000 ); public static final Set ALLOWED_ERROR_PATTERNS = ALLOWED_ERRORS.stream() @@ -84,27 +88,73 @@ public void test() throws IOException { List indices = availableIndices(); List lookupIndices = lookupIndices(); List policies = availableEnrichPolicies(); + CommandGenerator.QuerySchema mappingInfo = new CommandGenerator.QuerySchema(indices, lookupIndices, policies); + EsqlQueryGenerator.QueryExecuted previousResult = null; for (int i = 0; i < ITERATIONS; i++) { - String command = EsqlQueryGenerator.sourceCommand(indices); + List previousCommands = new ArrayList<>(); + CommandGenerator commandGenerator = EsqlQueryGenerator.sourceCommand(); + CommandGenerator.CommandDescription desc = commandGenerator.generate(List.of(), List.of(), mappingInfo); + String command = desc.commandString(); EsqlQueryGenerator.QueryExecuted result = execute(command, 0); if (result.exception() != null) { checkException(result); - continue; + break; + } + if (checkResults(List.of(), commandGenerator, desc, null, result).success() == false) { + break; } + previousResult = result; + previousCommands.add(desc); for (int j = 0; j < MAX_DEPTH; j++) { if (result.outputSchema().isEmpty()) { break; } - command = EsqlQueryGenerator.pipeCommand(result.outputSchema(), policies, lookupIndices); + commandGenerator = EsqlQueryGenerator.randomPipeCommandGenerator(); + desc = commandGenerator.generate(previousCommands, result.outputSchema(), mappingInfo); + if (desc == CommandGenerator.EMPTY_DESCRIPTION) { + continue; + } + command = desc.commandString(); result = execute(result.query() + command, result.depth() + 1); if (result.exception() != null) { checkException(result); break; } + if (checkResults(previousCommands, commandGenerator, desc, previousResult, result).success() == false) { + break; + } + previousCommands.add(desc); + previousResult = result; } } } + private static CommandGenerator.ValidationResult checkResults( + List previousCommands, + CommandGenerator commandGenerator, + CommandGenerator.CommandDescription commandDescription, + EsqlQueryGenerator.QueryExecuted previousResult, + EsqlQueryGenerator.QueryExecuted result + ) { + CommandGenerator.ValidationResult outputValidation = commandGenerator.validateOutput( + previousCommands, + commandDescription, + previousResult == null ? null : previousResult.outputSchema(), + previousResult == null ? null : previousResult.result(), + result.outputSchema(), + result.result() + ); + if (outputValidation.success() == false) { + for (Pattern allowedError : ALLOWED_ERROR_PATTERNS) { + if (allowedError.matcher(outputValidation.errorMessage()).matches()) { + return outputValidation; + } + } + fail("query: " + result.query() + "\nerror: " + outputValidation.errorMessage()); + } + return outputValidation; + } + private void checkException(EsqlQueryGenerator.QueryExecuted query) { for (Pattern allowedError : ALLOWED_ERROR_PATTERNS) { if (allowedError.matcher(query.exception().getMessage()).matches()) { @@ -114,16 +164,18 @@ private void checkException(EsqlQueryGenerator.QueryExecuted query) { fail("query: " + query.query() + "\nexception: " + query.exception().getMessage()); } + @SuppressWarnings("unchecked") private EsqlQueryGenerator.QueryExecuted execute(String command, int depth) { try { Map a = RestEsqlTestCase.runEsqlSync(new RestEsqlTestCase.RequestObjectBuilder().query(command).build()); List outputSchema = outputSchema(a); - return new EsqlQueryGenerator.QueryExecuted(command, depth, outputSchema, null); + List> values = (List>) a.get("values"); + return new EsqlQueryGenerator.QueryExecuted(command, depth, outputSchema, values, null); } catch (Exception e) { - return new EsqlQueryGenerator.QueryExecuted(command, depth, null, e); + return new EsqlQueryGenerator.QueryExecuted(command, depth, null, null, e); } catch (AssertionError ae) { // this is for ensureNoWarnings() - return new EsqlQueryGenerator.QueryExecuted(command, depth, null, new RuntimeException(ae.getMessage())); + return new EsqlQueryGenerator.QueryExecuted(command, depth, null, null, new RuntimeException(ae.getMessage())); } } @@ -144,7 +196,7 @@ private List availableIndices() throws IOException { .toList(); } - record LookupIdx(String idxName, String key, String keyType) {} + public record LookupIdx(String idxName, String key, String keyType) {} private List lookupIndices() { List result = new ArrayList<>(); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc new file mode 100644 index 0000000000000..0c01e7c524ce3 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/README.asciidoc @@ -0,0 +1,43 @@ += ES|QL Generative Tests + +These tests generate random queries and execute them. + +The intention is not to test the single commands, but rather to test how ES|QL query engine +(parser, optimizers, query layout, compute) manages very complex queries. + +The test workflow is the following: + +1. Generate a source command (eg. `FROM idx`) +2. Execute it +3. Check the result +4. Based on the previous query output, generate a pipe command (eg. `| EVAL foo = to_lower(bar))` +5. Append the command to the query and execute it +6. Check the result +7. If the query is less than N commands (see `GenerativeRestTest.MAX_DEPTH)`, go to point `4` + +This workflow is executed M times (see `GenerativeRestTest.ITERATIONS`) + +The result check happens at two levels: + +* query success/failure - If the query fails: + ** If the error is in `GenerativeRestTest.ALLOWED_ERRORS`, ignore it and start with next iteration. + ** Otherwise throw an assertion error +* check result correctness - this is delegated to last executed command generator + +== Implementing your own command generator + +If you implement a new command, and you want it to be tested by the generative tests, you can add a command generator here. + +All you have to do is: + +* add a class in `org.elasticsearch.xpack.esql.qa.rest.generative.command.source` (if it's a source command) or in `org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe` (if it's a pipe command) +* Implement `CommandGenerator` interface (see its javadoc, it should be explicative. Or just have a look at one of the existing commands, eg. `SortGenerator`) +** Implement `CommandGenerator.generate()` method, that will return the command. +*** Have a look at `EsqlQueryGenerator`, it contains many utility methods that will help you generate random expressions. +** Implement `CommandGenerator.validateOutput()` to validate the output of the query. +* Add your class to `EsqlQueryGenerator.SOURCE_COMMANDS` (if it's a source command) or `EsqlQueryGenerator.PIPE_COMMANDS` (if it's a pipe command). +* Run `GenerativeIT` at least a couple of times: these tests can be pretty noisy. +* If you get unexpected errors (real bugs in ES|QL), please open an issue and add the error to `GenerativeRestTest.ALLOWED_ERRORS`. Run tests again until everything works fine. + + +IMPORTANT: be careful when validating the output (Eg. the row count), as ES|QL can be quite non-deterministic when there are no SORTs diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java new file mode 100644 index 0000000000000..7d652fddbc87a --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/CommandGenerator.java @@ -0,0 +1,142 @@ +/* + * 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.qa.rest.generative.command; + +import org.elasticsearch.xpack.esql.CsvTestsDataLoader; +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.GenerativeRestTest; + +import java.util.List; +import java.util.Map; + +/** + * Implement this if you want to your command to be tested by the random query generator. + * Then add it to the right list in {@link EsqlQueryGenerator} + *

+ * The i + */ +public interface CommandGenerator { + + /** + * @param commandName the name of the command that is being generated + * @param commandString the full command string, including the "|" + * @param context additional information that could be useful for output validation. + * This will be passed to validateOutput after the query execution, together with the query output + */ + record CommandDescription(String commandName, CommandGenerator generator, String commandString, Map context) {} + + record QuerySchema( + List baseIndices, + List lookupIndices, + List enrichPolicies + ) {} + + record ValidationResult(boolean success, String errorMessage) {} + + CommandDescription EMPTY_DESCRIPTION = new CommandDescription("", new CommandGenerator() { + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + return EMPTY_DESCRIPTION; + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription command, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + return VALIDATION_OK; + } + }, "", Map.of()); + + ValidationResult VALIDATION_OK = new ValidationResult(true, null); + + /** + * Implement this method to generate a command, that will be appended to an existing query and then executed. + * See also {@link CommandDescription} + * + * @param previousCommands the list of the previous commands in the query + * @param previousOutput the output returned by the query so far. + * @param schema The columns returned by the query so far. It contains name and type information for each column. + * @return All the details about the generated command. See {@link CommandDescription}. + * If something goes wrong and for some reason you can't generate a command, you should return {@link CommandGenerator#EMPTY_DESCRIPTION} + */ + CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ); + + /** + * This will be invoked after the query execution. + * You are expected to put validation logic in here. + * + * @param previousCommands The list of commands before the last generated one. + * @param command The description of the command you just generated. + * It also contains the context information you stored during command generation. + * @param previousColumns The output schema of the original query (without last generated command). + * It contains name and type information for each column, see {@link EsqlQueryGenerator.Column} + * @param previousOutput The output of the original query (without last generated command), as a list (rows) of lists (columns) of values + * @param columns The output schema of the full query (WITH last generated command). + * @param output The output of the full query (WITH last generated command), as a list (rows) of lists (columns) of values + * @return The result of the output validation. If the validation succeeds, you should return {@link CommandGenerator#VALIDATION_OK}. + * Also, if for some reason you can't validate the output, just return {@link CommandGenerator#VALIDATION_OK}; for a command, having a generator without + * validation is much better than having no generator at all. + */ + ValidationResult validateOutput( + List previousCommands, + CommandDescription command, + List previousColumns, + List> previousOutput, + List columns, + List> output + ); + + static ValidationResult expectSameRowCount( + List previousCommands, + List> previousOutput, + List> output + ) { + + // ES|QL is quite non-deterministic in this sense, we can't guarantee it for now + // if (output.size() != previousOutput.size()) { + // return new ValidationResult(false, "Expecting [" + previousOutput.size() + "] rows, but got [" + output.size() + "]"); + // } + + return VALIDATION_OK; + } + + static ValidationResult expectSameColumns(List previousColumns, List columns) { + + if (previousColumns.stream().anyMatch(x -> x.name().contains(""))) { + return VALIDATION_OK; // known bug + } + + if (previousColumns.size() != columns.size()) { + return new ValidationResult(false, "Expecting [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + + List prevColNames = previousColumns.stream().map(EsqlQueryGenerator.Column::name).toList(); + List newColNames = columns.stream().map(EsqlQueryGenerator.Column::name).toList(); + if (prevColNames.equals(newColNames) == false) { + return new ValidationResult( + false, + "Expecting the following columns [" + String.join(", ", prevColNames) + "], got [" + String.join(", ", newColNames) + "]" + ); + } + + return VALIDATION_OK; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java new file mode 100644 index 0000000000000..83f8ae983dcde --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DissectGenerator.java @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class DissectGenerator implements CommandGenerator { + + public static final String DISSECT = "dissect"; + public static final CommandGenerator INSTANCE = new DissectGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + String field = EsqlQueryGenerator.randomStringField(previousOutput); + if (field == null) { + return EMPTY_DESCRIPTION;// no strings to dissect, just skip + } + StringBuilder result = new StringBuilder(" | dissect "); + result.append(field); + result.append(" \""); + for (int i = 0; i < randomIntBetween(1, 3); i++) { + if (i > 0) { + result.append(" "); + } + result.append("%{"); + String fieldName; + if (randomBoolean()) { + fieldName = EsqlQueryGenerator.randomIdentifier(); + } else { + fieldName = EsqlQueryGenerator.randomRawName(previousOutput); + if (fieldName == null) { + fieldName = EsqlQueryGenerator.randomIdentifier(); + } + } + result.append(fieldName); + result.append("}"); + } + result.append("\""); + String cmdString = result.toString(); + return new CommandDescription(DISSECT, this, cmdString, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + + if (previousColumns.size() > columns.size()) { + return new ValidationResult(false, "Expecting at least [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + + return CommandGenerator.expectSameRowCount(previousCommands, previousOutput, output); + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java new file mode 100644 index 0000000000000..8bf2597f808c0 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/DropGenerator.java @@ -0,0 +1,91 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class DropGenerator implements CommandGenerator { + + public static final String DROP = "drop"; + public static final String DROPPED_COLUMNS = "dropped_columns"; + + public static final CommandGenerator INSTANCE = new DropGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + if (previousOutput.size() < 2) { + return CommandGenerator.EMPTY_DESCRIPTION; // don't drop all of them, just do nothing + } + Set droppedColumns = new HashSet<>(); + int n = randomIntBetween(1, previousOutput.size() - 1); + Set proj = new HashSet<>(); + for (int i = 0; i < n; i++) { + String name = EsqlQueryGenerator.randomRawName(previousOutput); + if (name == null) { + continue; + } + if (name.length() > 1 && name.startsWith("`") == false && randomIntBetween(0, 100) < 10) { + if (randomBoolean()) { + name = name.substring(0, randomIntBetween(1, name.length() - 1)) + "*"; + } else { + name = "*" + name.substring(randomIntBetween(1, name.length() - 1)); + } + } else if (name.startsWith("`") == false && (randomBoolean() || name.isEmpty())) { + name = "`" + name + "`"; + } + proj.add(name); + droppedColumns.add(EsqlQueryGenerator.unquote(name)); + } + if (proj.isEmpty()) { + return CommandGenerator.EMPTY_DESCRIPTION; + } + String cmdString = " | drop " + proj.stream().collect(Collectors.joining(", ")); + return new CommandDescription(DROP, this, cmdString, Map.ofEntries(Map.entry(DROPPED_COLUMNS, droppedColumns))); + } + + @Override + @SuppressWarnings("unchecked") + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + Set droppedColumns = (Set) commandDescription.context().get(DROPPED_COLUMNS); + List resultColNames = columns.stream().map(EsqlQueryGenerator.Column::name).toList(); + // expected column names are unquoted already + for (String droppedColumn : droppedColumns) { + if (resultColNames.contains(droppedColumn)) { + return new ValidationResult(false, "Column [" + droppedColumn + "] was not dropped"); + } + } + // TODO awaits fix https://github.com/elastic/elasticsearch/issues/120272 + // return CommandGenerator.expectSameRowCount(previousOutput, output); + return VALIDATION_OK; + } + +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java new file mode 100644 index 0000000000000..aac8f16e13285 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EnrichGenerator.java @@ -0,0 +1,61 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomFrom; + +public class EnrichGenerator implements CommandGenerator { + + public static final String ENRICH = "enrich"; + public static final CommandGenerator INSTANCE = new EnrichGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + String field = EsqlQueryGenerator.randomKeywordField(previousOutput); + if (field == null || schema.enrichPolicies().isEmpty()) { + return EMPTY_DESCRIPTION; + } + + // TODO add WITH + String cmdString = " | enrich " + + randomFrom(EsqlQueryGenerator.policiesOnKeyword(schema.enrichPolicies())).policyName() + + " on " + + field; + return new CommandDescription(ENRICH, this, cmdString, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + + if (previousColumns.size() > columns.size()) { + return new ValidationResult(false, "Expecting at least [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + + return CommandGenerator.expectSameRowCount(previousCommands, previousOutput, output); + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java new file mode 100644 index 0000000000000..b49e313fa4e3f --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/EvalGenerator.java @@ -0,0 +1,96 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class EvalGenerator implements CommandGenerator { + + public static final String EVAL = "eval"; + public static final String NEW_COLUMNS = "new_columns"; + public static final CommandGenerator INSTANCE = new EvalGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + StringBuilder cmd = new StringBuilder(" | eval "); + int nFields = randomIntBetween(1, 10); + // TODO pass newly created fields to next expressions + var newColumns = new ArrayList<>(); + for (int i = 0; i < nFields; i++) { + String name; + if (randomBoolean()) { + name = EsqlQueryGenerator.randomIdentifier(); + } else { + name = EsqlQueryGenerator.randomName(previousOutput); + if (name == null) { + name = EsqlQueryGenerator.randomIdentifier(); + } + } + String expression = EsqlQueryGenerator.expression(previousOutput); + if (i > 0) { + cmd.append(","); + } + cmd.append(" "); + cmd.append(name); + newColumns.remove(unquote(name)); + newColumns.add(unquote(name)); + cmd.append(" = "); + cmd.append(expression); + } + String cmdString = cmd.toString(); + return new CommandDescription(EVAL, this, cmdString, Map.ofEntries(Map.entry(NEW_COLUMNS, newColumns))); + } + + @Override + @SuppressWarnings("unchecked") + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + List expectedColumns = (List) commandDescription.context().get(NEW_COLUMNS); + List resultColNames = columns.stream().map(EsqlQueryGenerator.Column::name).toList(); + List lastColumns = resultColNames.subList(resultColNames.size() - expectedColumns.size(), resultColNames.size()); + lastColumns = lastColumns.stream().map(EvalGenerator::unquote).toList(); + // expected column names are unquoted already + if (columns.size() < expectedColumns.size() || lastColumns.equals(expectedColumns) == false) { + return new ValidationResult( + false, + "Expecting the following as last columns [" + + String.join(", ", expectedColumns) + + "] but got [" + + String.join(", ", resultColNames) + + "]" + ); + } + + return CommandGenerator.expectSameRowCount(previousCommands, previousOutput, output); + } + + private static String unquote(String colName) { + if (colName.startsWith("`") && colName.endsWith("`")) { + return colName.substring(1, colName.length() - 1); + } + return colName; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java new file mode 100644 index 0000000000000..60322eb12c351 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/GrokGenerator.java @@ -0,0 +1,75 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class GrokGenerator implements CommandGenerator { + + public static final String GROK = "grok"; + public static final CommandGenerator INSTANCE = new GrokGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + String field = EsqlQueryGenerator.randomStringField(previousOutput); + if (field == null) { + return EMPTY_DESCRIPTION;// no strings to grok, just skip + } + StringBuilder result = new StringBuilder(" | grok "); + result.append(field); + result.append(" \""); + for (int i = 0; i < randomIntBetween(1, 3); i++) { + if (i > 0) { + result.append(" "); + } + result.append("%{WORD:"); + if (randomBoolean()) { + result.append(EsqlQueryGenerator.randomIdentifier()); + } else { + String fieldName = EsqlQueryGenerator.randomRawName(previousOutput); + if (fieldName == null) { + fieldName = EsqlQueryGenerator.randomIdentifier(); + } + result.append(fieldName); + } + result.append("}"); + } + result.append("\""); + String cmdString = result.toString(); + return new CommandDescription(GROK, this, cmdString, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + if (previousColumns.size() > columns.size()) { + return new ValidationResult(false, "Expecting at least [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + return CommandGenerator.expectSameRowCount(previousCommands, previousOutput, output); + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java new file mode 100644 index 0000000000000..f3f522576124e --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/KeepGenerator.java @@ -0,0 +1,82 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class KeepGenerator implements CommandGenerator { + + public static final String KEEP = "keep"; + + public static final CommandGenerator INSTANCE = new KeepGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + int n = randomIntBetween(1, previousOutput.size()); + Set proj = new HashSet<>(); + for (int i = 0; i < n; i++) { + if (randomIntBetween(0, 100) < 5) { + proj.add("*"); + } else { + String name = EsqlQueryGenerator.randomName(previousOutput); + if (name == null) { + continue; + } + if (name.length() > 1 && name.startsWith("`") == false && randomIntBetween(0, 100) < 10) { + if (randomBoolean()) { + name = name.substring(0, randomIntBetween(1, name.length() - 1)) + "*"; + } else { + name = "*" + name.substring(randomIntBetween(1, name.length() - 1)); + } + } + proj.add(name); + } + } + if (proj.isEmpty()) { + return EMPTY_DESCRIPTION; + } + String cmdString = " | keep " + proj.stream().collect(Collectors.joining(", ")); + return new CommandDescription(KEEP, this, cmdString, Map.of()); + } + + @Override + @SuppressWarnings("unchecked") + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + + if (previousColumns.size() < columns.size()) { + return new ValidationResult(false, "Expecting at most [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + + return VALIDATION_OK; + } + +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java new file mode 100644 index 0000000000000..d1e6fd8b3bfc8 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LimitGenerator.java @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class LimitGenerator implements CommandGenerator { + + public static final String LIMIT = "limit"; + public static final CommandGenerator INSTANCE = new LimitGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + int limit = randomIntBetween(0, 15000); + String cmd = " | limit " + limit; + return new CommandDescription(LIMIT, this, cmd, Map.ofEntries(Map.entry(LIMIT, limit))); + } + + @Override + @SuppressWarnings("unchecked") + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + int limit = (int) commandDescription.context().get(LIMIT); + boolean defaultLimit = false; + for (CommandDescription previousCommand : previousCommands) { + if (previousCommand.commandName().equals(LIMIT)) { + defaultLimit = true; + } + } + + if (previousOutput.size() > limit && output.size() != limit || defaultLimit && previousOutput.size() < output.size()) { + return new ValidationResult(false, "Expecting [" + limit + "] records, got [" + output.size() + "]"); + } + return CommandGenerator.expectSameColumns(previousColumns, columns); + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java new file mode 100644 index 0000000000000..4af6c5d73090d --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/LookupJoinGenerator.java @@ -0,0 +1,63 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.GenerativeRestTest; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomFrom; + +public class LookupJoinGenerator implements CommandGenerator { + + public static final String LOOKUP_JOIN = "lookup join"; + public static final CommandGenerator INSTANCE = new LookupJoinGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + GenerativeRestTest.LookupIdx lookupIdx = randomFrom(schema.lookupIndices()); + String lookupIdxName = lookupIdx.idxName(); + String idxKey = lookupIdx.key(); + String keyType = lookupIdx.keyType(); + + var candidateKeys = previousOutput.stream().filter(x -> x.type().equals(keyType)).toList(); + if (candidateKeys.isEmpty()) { + return EMPTY_DESCRIPTION; + } + EsqlQueryGenerator.Column key = randomFrom(candidateKeys); + String cmdString = "| rename " + key.name() + " as " + idxKey + " | lookup join " + lookupIdxName + " on " + idxKey; + return new CommandDescription(LOOKUP_JOIN, this, cmdString, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + + // the -1 is for the additional RENAME, that could drop one column + if (previousColumns.size() - 1 > columns.size()) { + return new ValidationResult(false, "Expecting at least [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + return VALIDATION_OK; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java new file mode 100644 index 0000000000000..317a2e459094e --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/MvExpandGenerator.java @@ -0,0 +1,51 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +public class MvExpandGenerator implements CommandGenerator { + + public static final String MV_EXPAND = "mv_expand"; + + public static final CommandGenerator INSTANCE = new MvExpandGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + String toExpand = EsqlQueryGenerator.randomName(previousOutput); + if (toExpand == null) { + return EMPTY_DESCRIPTION; // no columns to expand + } + String cmdString = " | mv_expand " + toExpand; + return new CommandDescription(MV_EXPAND, this, cmdString, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + return CommandGenerator.expectSameColumns(previousColumns, columns); + } + +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java new file mode 100644 index 0000000000000..80b36c06e524e --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/RenameGenerator.java @@ -0,0 +1,103 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomFrom; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class RenameGenerator implements CommandGenerator { + + public static final String RENAME = "rename"; + + public static final CommandGenerator INSTANCE = new RenameGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + int n = randomIntBetween(1, Math.min(3, previousOutput.size())); + List proj = new ArrayList<>(); + + Map nameToType = new HashMap<>(); + for (EsqlQueryGenerator.Column column : previousOutput) { + nameToType.put(column.name(), column.type()); + } + List names = new ArrayList<>( + previousOutput.stream() + .filter(EsqlQueryGenerator::fieldCanBeUsed) + .map(EsqlQueryGenerator.Column::name) + .collect(Collectors.toList()) + ); + if (names.isEmpty()) { + return EMPTY_DESCRIPTION; + } + for (int i = 0; i < n; i++) { + if (names.isEmpty()) { + break; + } + var name = randomFrom(names); + if (nameToType.get(name).endsWith("_range")) { + // ranges are not fully supported yet + continue; + } + names.remove(name); + + String newName; + if (names.isEmpty() || randomBoolean()) { + newName = EsqlQueryGenerator.randomIdentifier(); + names.add(newName); + } else { + newName = names.get(randomIntBetween(0, names.size() - 1)); + } + nameToType.put(newName, nameToType.get(name)); + if (randomBoolean() && name.startsWith("`") == false) { + name = "`" + name + "`"; + } + if (randomBoolean() && newName.startsWith("`") == false) { + newName = "`" + newName + "`"; + } + proj.add(name + " AS " + newName); + } + if (proj.isEmpty()) { + return EMPTY_DESCRIPTION; + } + String cmdString = " | rename " + proj.stream().collect(Collectors.joining(", ")); + return new CommandDescription(RENAME, this, cmdString, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + if (commandDescription == EMPTY_DESCRIPTION) { + return VALIDATION_OK; + } + if (previousColumns.size() < columns.size()) { + return new ValidationResult(false, "Expecting at most [" + previousColumns.size() + "] columns, got [" + columns.size() + "]"); + } + return CommandGenerator.expectSameRowCount(previousCommands, previousOutput, output); + } + +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java new file mode 100644 index 0000000000000..f7849d1c202f1 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/SortGenerator.java @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.ESTestCase.randomFrom; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class SortGenerator implements CommandGenerator { + + public static final String SORT = "sort"; + public static final CommandGenerator INSTANCE = new SortGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + int n = randomIntBetween(1, previousOutput.size()); + Set proj = new HashSet<>(); + for (int i = 0; i < n; i++) { + String col = EsqlQueryGenerator.randomSortableName(previousOutput); + if (col == null) { + return EMPTY_DESCRIPTION; // no sortable columns + } + proj.add(col); + } + String cmd = " | sort " + + proj.stream() + .map(x -> x + randomFrom("", " ASC", " DESC") + randomFrom("", " NULLS FIRST", " NULLS LAST")) + .collect(Collectors.joining(", ")); + return new CommandDescription(SORT, this, cmd, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + return CommandGenerator.expectSameColumns(previousColumns, columns); + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java new file mode 100644 index 0000000000000..b0ce7f43af997 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/StatsGenerator.java @@ -0,0 +1,80 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class StatsGenerator implements CommandGenerator { + + public static final String STATS = "stats"; + public static final CommandGenerator INSTANCE = new StatsGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + List nonNull = previousOutput.stream() + .filter(EsqlQueryGenerator::fieldCanBeUsed) + .filter(x -> x.type().equals("null") == false) + .collect(Collectors.toList()); + if (nonNull.isEmpty()) { + return EMPTY_DESCRIPTION; + } + StringBuilder cmd = new StringBuilder(" | stats "); + int nStats = randomIntBetween(1, 5); + for (int i = 0; i < nStats; i++) { + String name; + if (randomBoolean()) { + name = EsqlQueryGenerator.randomIdentifier(); + } else { + name = EsqlQueryGenerator.randomName(previousOutput); + if (name == null) { + name = EsqlQueryGenerator.randomIdentifier(); + } + } + String expression = EsqlQueryGenerator.agg(nonNull); + if (i > 0) { + cmd.append(","); + } + cmd.append(" "); + cmd.append(name); + cmd.append(" = "); + cmd.append(expression); + } + if (randomBoolean()) { + var col = EsqlQueryGenerator.randomGroupableName(nonNull); + if (col != null) { + cmd.append(" by " + col); + } + } + return new CommandDescription(STATS, this, cmd.toString(), Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + // TODO validate columns + return VALIDATION_OK; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java new file mode 100644 index 0000000000000..9bba468de0412 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/pipe/WhereGenerator.java @@ -0,0 +1,63 @@ +/* + * 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.qa.rest.generative.command.pipe; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomBoolean; +import static org.elasticsearch.test.ESTestCase.randomIntBetween; + +public class WhereGenerator implements CommandGenerator { + + public static final String WHERE = "where"; + public static final CommandGenerator INSTANCE = new WhereGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + // TODO more complex conditions + StringBuilder result = new StringBuilder(" | where "); + int nConditions = randomIntBetween(1, 5); + for (int i = 0; i < nConditions; i++) { + String exp = EsqlQueryGenerator.booleanExpression(previousOutput); + if (exp == null) { + // cannot generate expressions, just skip + return EMPTY_DESCRIPTION; + } + if (i > 0) { + result.append(randomBoolean() ? " AND " : " OR "); + } + if (randomBoolean()) { + result.append(" NOT "); + } + result.append(exp); + } + + String cmd = result.toString(); + return new CommandDescription(WHERE, this, cmd, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + return CommandGenerator.expectSameColumns(previousColumns, columns); + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java new file mode 100644 index 0000000000000..05cd307a50755 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/command/source/FromGenerator.java @@ -0,0 +1,54 @@ +/* + * 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.qa.rest.generative.command.source; + +import org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator; +import org.elasticsearch.xpack.esql.qa.rest.generative.command.CommandGenerator; + +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ESTestCase.randomIntBetween; +import static org.elasticsearch.xpack.esql.qa.rest.generative.EsqlQueryGenerator.indexPattern; + +public class FromGenerator implements CommandGenerator { + + public static final FromGenerator INSTANCE = new FromGenerator(); + + @Override + public CommandDescription generate( + List previousCommands, + List previousOutput, + QuerySchema schema + ) { + StringBuilder result = new StringBuilder("from "); + int items = randomIntBetween(1, 3); + List availableIndices = schema.baseIndices(); + for (int i = 0; i < items; i++) { + String pattern = indexPattern(availableIndices.get(randomIntBetween(0, availableIndices.size() - 1))); + if (i > 0) { + result.append(","); + } + result.append(pattern); + } + String query = result.toString(); + return new CommandDescription("from", this, query, Map.of()); + } + + @Override + public ValidationResult validateOutput( + List previousCommands, + CommandDescription commandDescription, + List previousColumns, + List> previousOutput, + List columns, + List> output + ) { + return VALIDATION_OK; + } +} From 0eebc8c9fc9a8c78bade4c5ac751925d839f15d6 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Fri, 6 Jun 2025 18:13:29 +0200 Subject: [PATCH 010/102] Add a test of LOOKUP JOIN against a time series index (#129007) Add a spec test of `LOOKUP JOIN` against a time series index. --- .../src/main/resources/lookup-join.csv-spec | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec index fc9932e330295..4ccbf29fca5ac 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec @@ -4585,6 +4585,37 @@ emp_no:integer | language_code:integer | language_name:keyword 10004 |null |null ; +lookupJoinOnTimeSeriesIndex +required_capability: join_lookup_v12 + +FROM k8s +| RENAME network.bytes_in AS language_code +| WHERE language_code < 10 +| LOOKUP JOIN languages_lookup ON language_code +| DROP network*, event +| SORT language_code, @timestamp +; + +@timestamp:date |client.ip:ip |cluster:keyword|language_code:long|pod:keyword |language_name:keyword +2024-05-10T00:17:14.000Z|10.10.20.35 |prod |0 |one |null +2024-05-10T00:07:33.000Z|10.10.20.32 |qa |1 |two |English +2024-05-10T00:20:00.000Z|10.10.20.35 |staging |1 |three |English +2024-05-10T00:18:02.000Z|10.10.20.33 |qa |2 |one |French +2024-05-10T00:04:49.000Z|10.10.20.34 |prod |3 |one |Spanish +2024-05-10T00:05:16.000Z|10.10.20.35 |prod |3 |one |Spanish +2024-05-10T00:05:58.000Z|10.10.20.30 |qa |3 |two |Spanish +2024-05-10T00:06:07.000Z|10.10.20.32 |staging |3 |two |Spanish +2024-05-10T00:07:19.000Z|10.10.20.32 |qa |3 |one |Spanish +2024-05-10T00:09:58.000Z|10.10.20.35 |prod |3 |one |Spanish +2024-05-10T00:16:55.000Z|10.10.20.32 |staging |3 |two |Spanish +2024-05-10T00:22:42.000Z|10.10.20.30 |staging |3 |three |Spanish +2024-05-10T00:22:49.000Z|10.10.20.34 |staging |3 |two |Spanish +2024-05-10T00:11:24.000Z|10.10.20.35 |qa |4 |two |German +2024-05-10T00:14:33.000Z|10.10.20.30 |prod |4 |two |German +2024-05-10T00:18:02.000Z|10.10.20.33 |staging |4 |one |German +2024-05-10T00:19:48.000Z|10.10.20.32 |prod |4 |three |German +; + ############################################### # LOOKUP JOIN on date_nanos field ############################################### From b1e15f00896d7746a399b81ded3bc8b8401a09f3 Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Fri, 6 Jun 2025 18:56:44 +0200 Subject: [PATCH 011/102] Make ILM `ClusterStateWaitStep` project-aware (#129042) This is part of an iterative process to make ILM project-aware. --- .../org/elasticsearch/test/ESTestCase.java | 7 ++ .../xpack/core/ilm/AllocationRoutedStep.java | 24 ++-- .../ilm/CheckNotDataStreamWriteIndexStep.java | 10 +- .../xpack/core/ilm/CheckShrinkReadyStep.java | 10 +- .../core/ilm/CheckTargetShardsCountStep.java | 6 +- .../xpack/core/ilm/ClusterStateWaitStep.java | 4 +- .../ClusterStateWaitUntilThresholdStep.java | 8 +- .../core/ilm/DataTierMigrationRoutedStep.java | 20 ++-- .../xpack/core/ilm/NoopStep.java | 4 +- .../core/ilm/ShrunkShardsAllocatedStep.java | 14 +-- .../core/ilm/ShrunkenIndexCheckStep.java | 13 +-- .../core/ilm/WaitForActiveShardsStep.java | 26 ++--- .../xpack/core/ilm/WaitForDataTierStep.java | 10 +- .../xpack/core/ilm/WaitForIndexColorStep.java | 13 +-- .../core/ilm/WaitForIndexingCompleteStep.java | 6 +- .../core/ilm/AllocationRoutedStepTests.java | 31 ++++-- .../CheckNoDataStreamWriteIndexStepTests.java | 48 ++++---- .../core/ilm/CheckShrinkReadyStepTests.java | 43 +++++--- .../ilm/CheckTargetShardsCountStepTests.java | 16 +-- ...usterStateWaitUntilThresholdStepTests.java | 43 +++----- .../ilm/DataTierMigrationRoutedStepTests.java | 94 +++++++--------- .../ilm/ShrunkShardsAllocatedStepTests.java | 43 ++++---- .../core/ilm/ShrunkenIndexCheckStepTests.java | 40 ++----- .../core/ilm/WaitForActiveShardsTests.java | 84 +++++++------- .../core/ilm/WaitForDataTierStepTests.java | 11 +- .../core/ilm/WaitForIndexColorStepTests.java | 103 ++++++++++-------- .../ilm/WaitForIndexingCompleteStepTests.java | 27 ++--- .../IndexLifecycleInitialisationTests.java | 9 +- .../xpack/ilm/ExecuteStepsUpdateTask.java | 2 +- .../xpack/ilm/IndexLifecycleRunnerTests.java | 2 +- 30 files changed, 365 insertions(+), 406 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 97a0d6b46d6b3..33cce5822c361 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -2829,4 +2829,11 @@ public static IndexSearcher newSearcher(IndexReader r, boolean maybeWrap, boolea public static ProjectState projectStateFromProject(ProjectMetadata.Builder project) { return ClusterState.builder(ClusterName.DEFAULT).putProjectMetadata(project).build().projectState(project.getId()); } + + /** + * Constructs an empty {@link ProjectState} with one (empty) project. + */ + public static ProjectState projectStateWithEmptyProject() { + return projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault())); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStep.java index 6922ee4cef3e4..5998f4cfebfe2 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStep.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.support.ActiveShardCount; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.IndexShardRoutingTable; @@ -45,18 +45,14 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata idxMeta = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata idxMeta = currentState.metadata().index(index); if (idxMeta == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); return new Result(false, null); } - if (ActiveShardCount.ALL.enoughShardsActive( - clusterState.metadata().getProject(), - clusterState.routingTable(), - index.getName() - ) == false) { + if (ActiveShardCount.ALL.enoughShardsActive(currentState.metadata(), currentState.routingTable(), index.getName()) == false) { logger.debug( "[{}] lifecycle action for index [{}] cannot make progress because not all shards are active", getKey().action(), @@ -68,12 +64,12 @@ public Result isConditionMet(Index index, ClusterState clusterState) { AllocationDeciders allocationDeciders = new AllocationDeciders( List.of( new FilterAllocationDecider( - clusterState.getMetadata().settings(), + currentState.cluster().metadata().settings(), new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS) ) ) ); - int allocationPendingAllShards = getPendingAllocations(index, allocationDeciders, clusterState); + int allocationPendingAllShards = getPendingAllocations(index, allocationDeciders, currentState); if (allocationPendingAllShards > 0) { logger.debug( @@ -89,14 +85,14 @@ public Result isConditionMet(Index index, ClusterState clusterState) { } } - static int getPendingAllocations(Index index, AllocationDeciders allocationDeciders, ClusterState clusterState) { + static int getPendingAllocations(Index index, AllocationDeciders allocationDeciders, ProjectState currentState) { // All the allocation attributes are already set so just need to check // if the allocation has happened - RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, clusterState, null, null, System.nanoTime()); + RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, currentState.cluster(), null, null, System.nanoTime()); int allocationPendingAllShards = 0; - final IndexRoutingTable indexRoutingTable = clusterState.getRoutingTable().index(index); + final IndexRoutingTable indexRoutingTable = currentState.routingTable().index(index); for (int shardId = 0; shardId < indexRoutingTable.size(); shardId++) { final IndexShardRoutingTable indexShardRoutingTable = indexRoutingTable.shard(shardId); for (int copy = 0; copy < indexShardRoutingTable.size(); copy++) { @@ -104,7 +100,7 @@ static int getPendingAllocations(Index index, AllocationDeciders allocationDecid String currentNodeId = shardRouting.currentNodeId(); boolean canRemainOnCurrentNode = allocationDeciders.canRemain( shardRouting, - clusterState.getRoutingNodes().node(currentNodeId), + currentState.cluster().getRoutingNodes().node(currentNodeId), allocation ).type() == Decision.Type.YES; if (canRemainOnCurrentNode == false || shardRouting.started() == false) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckNotDataStreamWriteIndexStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckNotDataStreamWriteIndexStep.java index d597ffeefbd5d..5cc7257981679 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckNotDataStreamWriteIndexStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckNotDataStreamWriteIndexStep.java @@ -8,11 +8,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.cluster.metadata.IndexAbstraction; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.common.Strings; import org.elasticsearch.index.Index; import org.elasticsearch.xpack.core.ilm.step.info.SingleMessageFieldInfo; @@ -39,9 +38,8 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - Metadata metadata = clusterState.metadata(); - IndexMetadata indexMetadata = metadata.getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata indexMetadata = currentState.metadata().index(index); String indexName = index.getName(); if (indexMetadata == null) { @@ -56,7 +54,7 @@ public Result isConditionMet(Index index, ClusterState clusterState) { } String policyName = indexMetadata.getLifecyclePolicyName(); - IndexAbstraction indexAbstraction = clusterState.metadata().getProject().getIndicesLookup().get(indexName); + IndexAbstraction indexAbstraction = currentState.metadata().getIndicesLookup().get(indexName); assert indexAbstraction != null : "invalid cluster metadata. index [" + indexName + "] was not found"; DataStream dataStream = indexAbstraction.getParentDataStream(); if (dataStream != null) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStep.java index b1c93bcb29236..3bf6744a93f68 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStep.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -51,8 +51,8 @@ public boolean isCompletable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata idxMeta = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata idxMeta = currentState.metadata().index(index); if (idxMeta == null) { // Index must have been since deleted, ignore it @@ -69,10 +69,10 @@ public Result isConditionMet(Index index, ClusterState clusterState) { throw new IllegalStateException("Cannot check shrink allocation as there are no allocation rules by _id"); } - var shutdown = clusterState.metadata().nodeShutdowns().get(idShardsShouldBeOn); + var shutdown = currentState.cluster().metadata().nodeShutdowns().get(idShardsShouldBeOn); boolean nodeBeingRemoved = shutdown != null && shutdown.getType() != SingleNodeShutdownMetadata.Type.RESTART; - final IndexRoutingTable routingTable = clusterState.getRoutingTable().index(index); + final IndexRoutingTable routingTable = currentState.routingTable().index(index); int foundShards = 0; for (ShardRouting shard : routingTable.shardsWithState(ShardRoutingState.STARTED)) { final String currentNodeId = shard.currentNodeId(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStep.java index 6377e758b7681..dd31084b6a1ac 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStep.java @@ -8,7 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.Strings; import org.elasticsearch.index.Index; @@ -40,8 +40,8 @@ public Integer getNumberOfShards() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata indexMetadata = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata indexMetadata = currentState.metadata().index(index); if (indexMetadata == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitStep.java index 4ed83fa170ead..f45fb62d7236f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitStep.java @@ -6,7 +6,7 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.index.Index; import org.elasticsearch.xcontent.ToXContentObject; @@ -21,7 +21,7 @@ public ClusterStateWaitStep(StepKey key, StepKey nextStepKey) { super(key, nextStepKey); } - public abstract Result isConditionMet(Index index, ClusterState clusterState); + public abstract Result isConditionMet(Index index, ProjectState currentState); /** * Whether the step can be completed at all. This only affects the diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStep.java index eb1d520abad0e..96b0df1448516 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStep.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; import org.elasticsearch.common.Strings; @@ -52,15 +52,15 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata idxMeta = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata idxMeta = currentState.metadata().index(index); if (idxMeta == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); return new Result(false, null); } - Result stepResult = stepToExecute.isConditionMet(index, clusterState); + Result stepResult = stepToExecute.isConditionMet(index, currentState); if (stepResult.complete() == false) { // checking the threshold after we execute the step to make sure we execute the wrapped step at least once (because time is a diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStep.java index 26dd4224e8e3b..7a6d2a29796e1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStep.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.support.ActiveShardCount; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.DesiredNodes; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.routing.allocation.decider.AllocationDeciders; @@ -45,8 +45,8 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata idxMeta = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata idxMeta = currentState.metadata().index(index); if (idxMeta == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); @@ -55,16 +55,12 @@ public Result isConditionMet(Index index, ClusterState clusterState) { List preferredTierConfiguration = idxMeta.getTierPreference(); Optional availableDestinationTier = DataTierAllocationDecider.preferredAvailableTier( preferredTierConfiguration, - clusterState.getNodes(), - DesiredNodes.latestFromClusterState(clusterState), - clusterState.metadata().nodeShutdowns() + currentState.cluster().getNodes(), + DesiredNodes.latestFromClusterState(currentState.cluster()), + currentState.cluster().metadata().nodeShutdowns() ); - if (ActiveShardCount.ALL.enoughShardsActive( - clusterState.metadata().getProject(), - clusterState.routingTable(), - index.getName() - ) == false) { + if (ActiveShardCount.ALL.enoughShardsActive(currentState.metadata(), currentState.routingTable(), index.getName()) == false) { if (preferredTierConfiguration.isEmpty()) { logger.debug( "[{}] lifecycle action for index [{}] cannot make progress because not all shards are active", @@ -103,7 +99,7 @@ public Result isConditionMet(Index index, ClusterState clusterState) { return new Result(true, null); } - int allocationPendingAllShards = getPendingAllocations(index, DECIDERS, clusterState); + int allocationPendingAllShards = getPendingAllocations(index, DECIDERS, currentState); if (allocationPendingAllShards > 0) { String statusMessage = availableDestinationTier.map( diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/NoopStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/NoopStep.java index 00abccca0790a..9be070c35345f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/NoopStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/NoopStep.java @@ -6,7 +6,7 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.index.Index; /** @@ -27,7 +27,7 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { + public Result isConditionMet(Index index, ProjectState currentState) { // We always want to move forward with this step so this should always be true return new Result(true, null); } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStep.java index 763e1c1f5ebfc..3e69ecdee7728 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStep.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.support.ActiveShardCount; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; import org.elasticsearch.common.Strings; @@ -42,8 +42,8 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata indexMetadata = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata indexMetadata = currentState.metadata().index(index); if (indexMetadata == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); @@ -55,16 +55,16 @@ public Result isConditionMet(Index index, ClusterState clusterState) { // We only want to make progress if all shards of the shrunk index are // active - boolean indexExists = clusterState.metadata().getProject().index(shrunkenIndexName) != null; + boolean indexExists = currentState.metadata().index(shrunkenIndexName) != null; if (indexExists == false) { return new Result(false, new Info(false, -1, false)); } boolean allShardsActive = ActiveShardCount.ALL.enoughShardsActive( - clusterState.metadata().getProject(), - clusterState.routingTable(), + currentState.metadata(), + currentState.routingTable(), shrunkenIndexName ); - int numShrunkIndexShards = clusterState.metadata().getProject().index(shrunkenIndexName).getNumberOfShards(); + int numShrunkIndexShards = currentState.metadata().index(shrunkenIndexName).getNumberOfShards(); if (allShardsActive) { return new Result(true, null); } else { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStep.java index efc106127881c..ad63c98f23f2e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStep.java @@ -8,7 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; import org.elasticsearch.common.Strings; @@ -41,24 +41,21 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata idxMeta = clusterState.getMetadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata idxMeta = currentState.metadata().index(index); if (idxMeta == null) { logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); // Index must have been since deleted, ignore it return new Result(false, null); } - String shrunkenIndexSource = IndexMetadata.INDEX_RESIZE_SOURCE_NAME.get( - clusterState.metadata().getProject().index(index).getSettings() - ); + String shrunkenIndexSource = IndexMetadata.INDEX_RESIZE_SOURCE_NAME.get(currentState.metadata().index(index).getSettings()); if (Strings.isNullOrEmpty(shrunkenIndexSource)) { throw new IllegalStateException("step[" + NAME + "] is checking an un-shrunken index[" + index.getName() + "]"); } LifecycleExecutionState lifecycleState = idxMeta.getLifecycleExecutionState(); String targetIndexName = getShrinkIndexName(shrunkenIndexSource, lifecycleState); - boolean isConditionMet = index.getName().equals(targetIndexName) - && clusterState.metadata().getProject().index(shrunkenIndexSource) == null; + boolean isConditionMet = index.getName().equals(targetIndexName) && currentState.metadata().index(shrunkenIndexSource) == null; if (isConditionMet) { return new Result(true, null); } else { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsStep.java index 49f2259ec4162..5735202b11948 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsStep.java @@ -9,11 +9,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.support.ActiveShardCount; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.cluster.metadata.IndexAbstraction; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.common.Strings; import org.elasticsearch.index.Index; @@ -48,9 +47,8 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - Metadata metadata = clusterState.metadata(); - IndexMetadata originalIndexMeta = metadata.getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata originalIndexMeta = currentState.metadata().index(index); if (originalIndexMeta == null) { String errorMessage = Strings.format( @@ -74,12 +72,12 @@ public Result isConditionMet(Index index, ClusterState clusterState) { return new Result(true, new SingleMessageFieldInfo(message)); } - IndexAbstraction indexAbstraction = metadata.getProject().getIndicesLookup().get(index.getName()); + IndexAbstraction indexAbstraction = currentState.metadata().getIndicesLookup().get(index.getName()); final String rolledIndexName; final String waitForActiveShardsSettingValue; DataStream dataStream = indexAbstraction.getParentDataStream(); if (dataStream != null) { - IndexAbstraction dataStreamAbstraction = metadata.getProject().getIndicesLookup().get(dataStream.getName()); + IndexAbstraction dataStreamAbstraction = currentState.metadata().getIndicesLookup().get(dataStream.getName()); assert dataStreamAbstraction != null : dataStream.getName() + " datastream is not present in the metadata indices lookup"; // Determine which write index we care about right now: final Index rolledIndex; @@ -91,7 +89,7 @@ public Result isConditionMet(Index index, ClusterState clusterState) { if (rolledIndex == null) { return getErrorResultOnNullMetadata(getKey(), index); } - IndexMetadata rolledIndexMeta = metadata.getProject().index(rolledIndex); + IndexMetadata rolledIndexMeta = currentState.metadata().index(rolledIndex); rolledIndexName = rolledIndexMeta.getIndex().getName(); waitForActiveShardsSettingValue = rolledIndexMeta.getSettings().get(IndexMetadata.SETTING_WAIT_FOR_ACTIVE_SHARDS.getKey()); } else { @@ -106,12 +104,12 @@ public Result isConditionMet(Index index, ClusterState clusterState) { ); } - IndexAbstraction aliasAbstraction = metadata.getProject().getIndicesLookup().get(rolloverAlias); + IndexAbstraction aliasAbstraction = currentState.metadata().getIndicesLookup().get(rolloverAlias); assert aliasAbstraction.getType() == IndexAbstraction.Type.ALIAS : rolloverAlias + " must be an alias but it is not"; Index aliasWriteIndex = aliasAbstraction.getWriteIndex(); if (aliasWriteIndex != null) { - IndexMetadata writeIndexImd = metadata.getProject().index(aliasWriteIndex); + IndexMetadata writeIndexImd = currentState.metadata().index(aliasWriteIndex); rolledIndexName = writeIndexImd.getIndex().getName(); waitForActiveShardsSettingValue = writeIndexImd.getSettings().get(IndexMetadata.SETTING_WAIT_FOR_ACTIVE_SHARDS.getKey()); } else { @@ -129,7 +127,7 @@ public Result isConditionMet(Index index, ClusterState clusterState) { return getErrorResultOnNullMetadata(getKey(), index); } rolledIndexName = tmpRolledIndex.getName(); - waitForActiveShardsSettingValue = metadata.getProject() + waitForActiveShardsSettingValue = currentState.metadata() .index(rolledIndexName) .getSettings() .get("index.write.wait_for_active_shards"); @@ -138,12 +136,12 @@ public Result isConditionMet(Index index, ClusterState clusterState) { ActiveShardCount activeShardCount = ActiveShardCount.parseString(waitForActiveShardsSettingValue); boolean enoughShardsActive = activeShardCount.enoughShardsActive( - clusterState.metadata().getProject(), - clusterState.routingTable(), + currentState.metadata(), + currentState.routingTable(), rolledIndexName ); - IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(rolledIndexName); + IndexRoutingTable indexRoutingTable = currentState.routingTable().index(rolledIndexName); int currentActiveShards = 0; for (int i = 0; i < indexRoutingTable.size(); i++) { currentActiveShards += indexRoutingTable.shard(i).activeShards().size(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStep.java index 923b57988b415..84bf405abbfbe 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStep.java @@ -7,7 +7,7 @@ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.DesiredNodes; import org.elasticsearch.cluster.routing.allocation.DataTier; import org.elasticsearch.index.Index; @@ -33,12 +33,12 @@ public WaitForDataTierStep(StepKey key, StepKey nextStepKey, String tierPreferen } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { + public Result isConditionMet(Index index, ProjectState currentState) { boolean present = DataTierAllocationDecider.preferredAvailableTier( DataTier.parseTierList(tierPreference), - clusterState.nodes(), - DesiredNodes.latestFromClusterState(clusterState), - clusterState.metadata().nodeShutdowns() + currentState.cluster().nodes(), + DesiredNodes.latestFromClusterState(currentState.cluster()), + currentState.cluster().metadata().nodeShutdowns() ).isPresent(); SingleMessageFieldInfo info = present ? null : new SingleMessageFieldInfo("no nodes for tiers [" + tierPreference + "] available"); return new Result(present, info); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStep.java index defe094c7ec63..5e8a383dc4cf9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStep.java @@ -9,7 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; @@ -81,13 +81,10 @@ public boolean equals(Object obj) { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - LifecycleExecutionState lifecycleExecutionState = clusterState.metadata() - .getProject() - .index(index.getName()) - .getLifecycleExecutionState(); + public Result isConditionMet(Index index, ProjectState currentState) { + LifecycleExecutionState lifecycleExecutionState = currentState.metadata().index(index.getName()).getLifecycleExecutionState(); String indexName = indexNameSupplier.apply(index.getName(), lifecycleExecutionState); - IndexMetadata indexMetadata = clusterState.metadata().getProject().index(indexName); + IndexMetadata indexMetadata = currentState.metadata().index(indexName); // check if the (potentially) derived index exists if (indexMetadata == null) { String errorMessage = Strings.format( @@ -100,7 +97,7 @@ public Result isConditionMet(Index index, ClusterState clusterState) { return new Result(false, new SingleMessageFieldInfo(errorMessage)); } - IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(indexMetadata.getIndex()); + IndexRoutingTable indexRoutingTable = currentState.routingTable().index(indexMetadata.getIndex()); Result result = switch (this.color) { case GREEN -> waitForGreen(indexRoutingTable); case YELLOW -> waitForYellow(indexRoutingTable); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStep.java index c1c86f04eaefd..3a3a6da4f1321 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStep.java @@ -8,7 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.index.Index; import org.elasticsearch.xcontent.ParseField; @@ -36,8 +36,8 @@ public boolean isRetryable() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - IndexMetadata followerIndex = clusterState.metadata().getProject().index(index); + public Result isConditionMet(Index index, ProjectState currentState) { + IndexMetadata followerIndex = currentState.metadata().index(index); if (followerIndex == null) { // Index must have been since deleted, ignore it logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().action(), index.getName()); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStepTests.java index 708c3630b8b8a..d9a201d24d061 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AllocationRoutedStepTests.java @@ -6,9 +6,12 @@ */ package org.elasticsearch.xpack.core.ilm; +import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -158,8 +161,9 @@ public void testClusterExcludeFiltersConditionMetOnlyOneCopyAllocated() { Settings clusterSettings = Settings.builder().put("cluster.routing.allocation.exclude._id", "node1").build(); Settings.Builder nodeSettingsBuilder = Settings.builder(); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().indices(indices).transientSettings(clusterSettings)) + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).indices(indices).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .metadata(Metadata.builder().put(project).transientSettings(clusterSettings)) .nodes( DiscoveryNodes.builder() .add( @@ -175,9 +179,10 @@ public void testClusterExcludeFiltersConditionMetOnlyOneCopyAllocated() { .build() ) ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); - Result actualResult = step.isConditionMet(index, clusterState); + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); + Result actualResult = step.isConditionMet(index, state); Result expectedResult = new ClusterStateWaitStep.Result(false, allShardsActiveAllocationInfo(1, 1)); assertEquals(expectedResult.complete(), actualResult.complete()); @@ -489,11 +494,11 @@ public void testExecuteReplicasNotAllocatedOnSingleNode() { public void testExecuteIndexMissing() throws Exception { Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).build(); + ProjectState state = projectStateWithEmptyProject(); AllocationRoutedStep step = createRandomInstance(); - Result actualResult = step.isConditionMet(index, clusterState); + Result actualResult = step.isConditionMet(index, state); assertFalse(actualResult.complete()); assertNull(actualResult.informationContext()); } @@ -516,8 +521,9 @@ private void assertAllocateStatus( .build(); Map indices = Map.of(index.getName(), indexMetadata); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().indices(indices)) + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).indices(indices).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) .nodes( DiscoveryNodes.builder() .add( @@ -533,9 +539,10 @@ private void assertAllocateStatus( .build() ) ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); - Result actualResult = step.isConditionMet(index, clusterState); + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); + Result actualResult = step.isConditionMet(index, state); assertEquals(expectedResult.complete(), actualResult.complete()); assertEquals(expectedResult.informationContext(), actualResult.informationContext()); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckNoDataStreamWriteIndexStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckNoDataStreamWriteIndexStepTests.java index 54c6ceb814af8..077d59c4a158d 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckNoDataStreamWriteIndexStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckNoDataStreamWriteIndexStepTests.java @@ -6,10 +6,10 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.xpack.core.ilm.step.info.SingleMessageFieldInfo; @@ -54,11 +54,9 @@ public void testStepCompleteIfIndexIsNotPartOfDataStream() { .numberOfReplicas(randomIntBetween(0, 5)) .build(); - ClusterState clusterState = ClusterState.builder(emptyClusterState()) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, true)); - ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), is(nullValue())); } @@ -80,20 +78,17 @@ public void testStepIncompleteIfIndexIsTheDataStreamWriteIndex() { .numberOfReplicas(randomIntBetween(0, 5)) .build(); - ClusterState clusterState = ClusterState.builder(emptyClusterState()) - .metadata( - Metadata.builder() - .put(indexMetadata, true) - .put(failureIndexMetadata, true) - .put(newInstance(dataStreamName, List.of(indexMetadata.getIndex()), List.of(failureIndexMetadata.getIndex()))) - .build() - ) - .build(); + ProjectState state = projectStateFromProject( + ProjectMetadata.builder(randomUniqueProjectId()) + .put(indexMetadata, true) + .put(failureIndexMetadata, true) + .put(newInstance(dataStreamName, List.of(indexMetadata.getIndex()), List.of(failureIndexMetadata.getIndex()))) + ); boolean useFailureStore = randomBoolean(); IndexMetadata indexToOperateOn = useFailureStore ? failureIndexMetadata : indexMetadata; String expectedIndexName = indexToOperateOn.getIndex().getName(); - ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(indexToOperateOn.getIndex(), clusterState); + ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(indexToOperateOn.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat( @@ -146,21 +141,18 @@ public void testStepCompleteIfPartOfDataStreamButNotWriteIndex() { List backingIndices = List.of(indexMetadata.getIndex(), writeIndexMetadata.getIndex()); List failureIndices = List.of(failureIndexMetadata.getIndex(), failureStoreWriteIndexMetadata.getIndex()); - ClusterState clusterState = ClusterState.builder(emptyClusterState()) - .metadata( - Metadata.builder() - .put(indexMetadata, true) - .put(writeIndexMetadata, true) - .put(failureIndexMetadata, true) - .put(failureStoreWriteIndexMetadata, true) - .put(newInstance(dataStreamName, backingIndices, failureIndices)) - .build() - ) - .build(); + ProjectState state = projectStateFromProject( + ProjectMetadata.builder(randomUniqueProjectId()) + .put(indexMetadata, true) + .put(writeIndexMetadata, true) + .put(failureIndexMetadata, true) + .put(failureStoreWriteIndexMetadata, true) + .put(newInstance(dataStreamName, backingIndices, failureIndices)) + ); boolean useFailureStore = randomBoolean(); IndexMetadata indexToOperateOn = useFailureStore ? failureIndexMetadata : indexMetadata; - ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(indexToOperateOn.getIndex(), clusterState); + ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(indexToOperateOn.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), is(nullValue())); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStepTests.java index a427f8c9154cc..42bbdd472fa15 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckShrinkReadyStepTests.java @@ -7,10 +7,13 @@ package org.elasticsearch.xpack.core.ilm; +import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; @@ -412,11 +415,11 @@ public void testExecuteReplicasButCopiesNotPresent() { public void testExecuteIndexMissing() throws Exception { Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).build(); + ProjectState state = projectStateWithEmptyProject(); CheckShrinkReadyStep step = createRandomInstance(); - ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, clusterState); + ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, state); assertFalse(actualResult.complete()); assertNull(actualResult.informationContext()); } @@ -448,13 +451,14 @@ public void testStepCompletableIfAllShardsActive() { .numberOfReplicas(1) .build(); Map indices = Map.of(index.getName(), indexMetadata); + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).indices(indices).build(); final String targetNodeName = type == SingleNodeShutdownMetadata.Type.REPLACE ? randomAlphaOfLengthBetween(10, 20) : null; final TimeValue grace = type == SIGTERM ? randomTimeValue() : null; - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) .metadata( Metadata.builder() - .indices(indices) + .put(project) .putCustom( NodesShutdownMetadata.TYPE, new NodesShutdownMetadata( @@ -492,10 +496,11 @@ public void testStepCompletableIfAllShardsActive() { .build() ) ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); assertTrue(step.isCompletable()); - ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, clusterState); + ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, state); assertTrue(actualResult.complete()); assertTrue(step.isCompletable()); } @@ -528,13 +533,14 @@ public void testStepBecomesUncompletable() { .numberOfReplicas(1) .build(); Map indices = Map.of(index.getName(), indexMetadata); + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).indices(indices).build(); final String targetNodeName = type == SingleNodeShutdownMetadata.Type.REPLACE ? randomAlphaOfLengthBetween(10, 20) : null; final TimeValue grace = type == SIGTERM ? randomTimeValue() : null; - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) .metadata( Metadata.builder() - .indices(indices) + .put(project) .putCustom( NodesShutdownMetadata.TYPE, new NodesShutdownMetadata( @@ -572,10 +578,11 @@ public void testStepBecomesUncompletable() { .build() ) ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); assertTrue(step.isCompletable()); - ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, clusterState); + ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, state); assertFalse(actualResult.complete()); assertThat( Strings.toString(actualResult.informationContext()), @@ -602,9 +609,10 @@ private void assertAllocateStatus( .numberOfReplicas(replicas) .build(); Map indices = Map.of(index.getName(), indexMetadata); + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).indices(indices).build(); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().indices(indices)) + ProjectState state = ClusterState.builder(ClusterState.EMPTY_STATE) + .putProjectMetadata(project) .nodes( DiscoveryNodes.builder() .add( @@ -624,9 +632,10 @@ private void assertAllocateStatus( .build() ) ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); - ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, clusterState); + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); + ClusterStateWaitStep.Result actualResult = step.isConditionMet(index, state); assertEquals(expectedResult.complete(), actualResult.complete()); assertEquals(expectedResult.informationContext(), actualResult.informationContext()); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStepTests.java index 23d24fbd28730..791f9ee2bfa90 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/CheckTargetShardsCountStepTests.java @@ -6,9 +6,9 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.xpack.core.ilm.Step.StepKey; import org.elasticsearch.xpack.core.ilm.step.info.SingleMessageFieldInfo; @@ -49,13 +49,11 @@ public void testStepCompleteIfTargetShardsCountIsValid() { .numberOfReplicas(randomIntBetween(0, 5)) .build(); - ClusterState clusterState = ClusterState.builder(emptyClusterState()) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); CheckTargetShardsCountStep checkTargetShardsCountStep = new CheckTargetShardsCountStep(randomStepKey(), randomStepKey(), 2); - ClusterStateWaitStep.Result result = checkTargetShardsCountStep.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = checkTargetShardsCountStep.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); } @@ -68,13 +66,11 @@ public void testStepIncompleteIfTargetShardsCountNotValid() { .numberOfReplicas(randomIntBetween(0, 5)) .build(); - ClusterState clusterState = ClusterState.builder(emptyClusterState()) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); CheckTargetShardsCountStep checkTargetShardsCountStep = new CheckTargetShardsCountStep(randomStepKey(), randomStepKey(), 3); - ClusterStateWaitStep.Result result = checkTargetShardsCountStep.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = checkTargetShardsCountStep.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat( diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStepTests.java index eeddda4199665..e44308d9d8544 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ClusterStateWaitUntilThresholdStepTests.java @@ -6,11 +6,10 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.core.TimeValue; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexVersion; @@ -67,7 +66,7 @@ public void testIndexIsMissingReturnsIncompleteResult() { ClusterStateWaitUntilThresholdStep underTest = new ClusterStateWaitUntilThresholdStep(stepToExecute, randomStepKey()); ClusterStateWaitStep.Result result = underTest.isConditionMet( new Index("testName", UUID.randomUUID().toString()), - ClusterState.EMPTY_STATE + projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId())) ); assertThat(result.complete(), is(false)); assertThat(result.informationContext(), nullValue()); @@ -86,14 +85,12 @@ public void testIsConditionMetForUnderlyingStep() { .numberOfShards(1) .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, true)); WaitForIndexingCompleteStep stepToExecute = new WaitForIndexingCompleteStep(randomStepKey(), randomStepKey()); ClusterStateWaitUntilThresholdStep underTest = new ClusterStateWaitUntilThresholdStep(stepToExecute, randomStepKey()); - ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); } @@ -111,13 +108,11 @@ public void testIsConditionMetForUnderlyingStep() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, true)); WaitForIndexingCompleteStep stepToExecute = new WaitForIndexingCompleteStep(randomStepKey(), randomStepKey()); ClusterStateWaitUntilThresholdStep underTest = new ClusterStateWaitUntilThresholdStep(stepToExecute, randomStepKey()); - ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); assertThat(result.informationContext(), notNullValue()); @@ -144,15 +139,13 @@ public void testIsConditionMetForUnderlyingStep() { .numberOfShards(1) .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, true)); WaitForIndexingCompleteStep stepToExecute = new WaitForIndexingCompleteStep(randomStepKey(), randomStepKey()); StepKey nextKeyOnThresholdBreach = randomStepKey(); ClusterStateWaitUntilThresholdStep underTest = new ClusterStateWaitUntilThresholdStep(stepToExecute, nextKeyOnThresholdBreach); - ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); assertThat(underTest.getNextStepKey(), is(not(nextKeyOnThresholdBreach))); @@ -173,15 +166,13 @@ public void testIsConditionMetForUnderlyingStep() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, true)); StepKey currentStepKey = randomStepKey(); WaitForIndexingCompleteStep stepToExecute = new WaitForIndexingCompleteStep(currentStepKey, randomStepKey()); StepKey nextKeyOnThresholdBreach = randomStepKey(); ClusterStateWaitUntilThresholdStep underTest = new ClusterStateWaitUntilThresholdStep(stepToExecute, nextKeyOnThresholdBreach); - ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = underTest.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), notNullValue()); @@ -247,14 +238,12 @@ public void testIsCompletableBreaches() { .putCustom(ILM_CUSTOM_METADATA_KEY, Map.of("step_time", String.valueOf(Clock.systemUTC().millis()))) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, true)); ClusterStateWaitUntilThresholdStep step = new ClusterStateWaitUntilThresholdStep( new ClusterStateWaitStep(new StepKey("phase", "action", "key"), new StepKey("phase", "action", "next-key")) { @Override - public Result isConditionMet(Index index, ClusterState clusterState) { + public Result isConditionMet(Index index, ProjectState currentState) { return new Result(false, new SingleMessageFieldInfo("")); } @@ -266,14 +255,14 @@ public boolean isRetryable() { new StepKey("phase", "action", "breached") ); - assertFalse(step.isConditionMet(indexMetadata.getIndex(), clusterState).complete()); + assertFalse(step.isConditionMet(indexMetadata.getIndex(), state).complete()); assertThat(step.getNextStepKey().name(), equalTo("next-key")); step = new ClusterStateWaitUntilThresholdStep( new ClusterStateWaitStep(new StepKey("phase", "action", "key"), new StepKey("phase", "action", "next-key")) { @Override - public Result isConditionMet(Index index, ClusterState clusterState) { + public Result isConditionMet(Index index, ProjectState currentState) { return new Result(false, new SingleMessageFieldInfo("")); } @@ -289,7 +278,7 @@ public boolean isRetryable() { }, new StepKey("phase", "action", "breached") ); - assertTrue(step.isConditionMet(indexMetadata.getIndex(), clusterState).complete()); + assertTrue(step.isConditionMet(indexMetadata.getIndex(), state).complete()); assertThat(step.getNextStepKey().name(), equalTo("breached")); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStepTests.java index 51d1464ed5525..8689d083c5a5a 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/DataTierMigrationRoutedStepTests.java @@ -6,9 +6,11 @@ */ package org.elasticsearch.xpack.core.ilm; +import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; @@ -29,6 +31,9 @@ import java.util.Collections; import java.util.Set; +import static org.elasticsearch.cluster.node.DiscoveryNodeRole.DATA_HOT_NODE_ROLE; +import static org.elasticsearch.cluster.node.DiscoveryNodeRole.DATA_ROLE; +import static org.elasticsearch.cluster.node.DiscoveryNodeRole.DATA_WARM_NODE_ROLE; import static org.elasticsearch.cluster.routing.TestShardRouting.shardRoutingBuilder; import static org.elasticsearch.cluster.routing.allocation.DataTier.TIER_PREFERENCE; import static org.elasticsearch.xpack.core.ilm.CheckShrinkReadyStepTests.randomUnassignedInfo; @@ -80,15 +85,11 @@ public void testExecuteWithUnassignedShard() { ).build() ); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes(DiscoveryNodes.builder().add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_HOT_NODE_ROLE)))) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_HOT_NODE_ROLE); DataTierMigrationRoutedStep step = createRandomInstance(); Result expectedResult = new Result(false, waitingForActiveShardsAllocationInfo(1)); - Result actualResult = step.isConditionMet(index, clusterState); + Result actualResult = step.isConditionMet(index, state); assertThat(actualResult.complete(), is(false)); assertThat(actualResult.informationContext(), is(expectedResult.informationContext())); } @@ -103,15 +104,7 @@ public void testExecuteWithPendingShards() { IndexRoutingTable.Builder indexRoutingTable = IndexRoutingTable.builder(index) .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, ShardRoutingState.STARTED)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes( - DiscoveryNodes.builder() - .add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_HOT_NODE_ROLE))) - .add(newNode("node2", Collections.singleton(DiscoveryNodeRole.DATA_WARM_NODE_ROLE))) - ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_HOT_NODE_ROLE, DATA_WARM_NODE_ROLE); DataTierMigrationRoutedStep step = createRandomInstance(); Result expectedResult = new Result( false, @@ -128,7 +121,7 @@ public void testExecuteWithPendingShards() { ) ); - Result actualResult = step.isConditionMet(index, clusterState); + Result actualResult = step.isConditionMet(index, state); assertThat(actualResult.complete(), is(false)); assertThat(actualResult.informationContext(), is(expectedResult.informationContext())); } @@ -143,11 +136,7 @@ public void testExecuteWithPendingShardsAndTargetRoleNotPresentInCluster() { IndexRoutingTable.Builder indexRoutingTable = IndexRoutingTable.builder(index) .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, ShardRoutingState.STARTED)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes(DiscoveryNodes.builder().add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_HOT_NODE_ROLE)))) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_HOT_NODE_ROLE); DataTierMigrationRoutedStep step = createRandomInstance(); Result expectedResult = new Result( false, @@ -162,18 +151,18 @@ public void testExecuteWithPendingShardsAndTargetRoleNotPresentInCluster() { ) ); - Result actualResult = step.isConditionMet(index, clusterState); + Result actualResult = step.isConditionMet(index, state); assertThat(actualResult.complete(), is(false)); assertThat(actualResult.informationContext(), is(expectedResult.informationContext())); } public void testExecuteIndexMissing() { Index index = new Index(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE).build(); + ProjectState state = projectStateWithEmptyProject(); DataTierMigrationRoutedStep step = createRandomInstance(); - Result actualResult = step.isConditionMet(index, clusterState); + Result actualResult = step.isConditionMet(index, state); assertThat(actualResult.complete(), is(false)); assertThat(actualResult.informationContext(), is(nullValue())); } @@ -188,17 +177,9 @@ public void testExecuteIsComplete() { IndexRoutingTable.Builder indexRoutingTable = IndexRoutingTable.builder(index) .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node2", true, ShardRoutingState.STARTED)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes( - DiscoveryNodes.builder() - .add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_HOT_NODE_ROLE))) - .add(newNode("node2", Collections.singleton(DiscoveryNodeRole.DATA_WARM_NODE_ROLE))) - ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_HOT_NODE_ROLE, DATA_WARM_NODE_ROLE); DataTierMigrationRoutedStep step = createRandomInstance(); - Result result = step.isConditionMet(index, clusterState); + Result result = step.isConditionMet(index, state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), is(nullValue())); } @@ -213,13 +194,10 @@ public void testExecuteWithGenericDataNodes() { IndexRoutingTable.Builder indexRoutingTable = IndexRoutingTable.builder(index) .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, ShardRoutingState.STARTED)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes(DiscoveryNodes.builder().add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_ROLE)))) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_ROLE); + DataTierMigrationRoutedStep step = createRandomInstance(); - Result result = step.isConditionMet(index, clusterState); + Result result = step.isConditionMet(index, state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), is(nullValue())); } @@ -236,15 +214,11 @@ public void testExecuteForIndexWithoutTierRoutingInformationWaitsForReplicasToBe .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, ShardRoutingState.STARTED)) .addReplica(ShardRouting.Role.DEFAULT); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes(DiscoveryNodes.builder().add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_HOT_NODE_ROLE)))) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_HOT_NODE_ROLE); DataTierMigrationRoutedStep step = createRandomInstance(); Result expectedResult = new Result(false, waitingForActiveShardsAllocationInfo(1)); - Result result = step.isConditionMet(index, clusterState); + Result result = step.isConditionMet(index, state); assertThat(result.complete(), is(false)); assertThat(result.informationContext(), is(expectedResult.informationContext())); } @@ -254,23 +228,29 @@ public void testExecuteForIndexWithoutTierRoutingInformationWaitsForReplicasToBe .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node1", true, ShardRoutingState.STARTED)) .addShard(TestShardRouting.newShardRouting(new ShardId(index, 0), "node2", false, ShardRoutingState.STARTED)); - ClusterState clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .nodes( - DiscoveryNodes.builder() - .add(newNode("node1", Collections.singleton(DiscoveryNodeRole.DATA_HOT_NODE_ROLE))) - .add(newNode("node2", Collections.singleton(DiscoveryNodeRole.DATA_WARM_NODE_ROLE))) - ) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + ProjectState state = createState(indexMetadata, indexRoutingTable, DATA_HOT_NODE_ROLE, DATA_WARM_NODE_ROLE); DataTierMigrationRoutedStep step = createRandomInstance(); - Result result = step.isConditionMet(index, clusterState); + Result result = step.isConditionMet(index, state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), is(nullValue())); } } + private ProjectState createState(IndexMetadata indexMetadata, IndexRoutingTable.Builder indexRoutingTable, DiscoveryNodeRole... roles) { + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false).build(); + final var nodes = DiscoveryNodes.builder(); + for (int i = 0; i < roles.length; i++) { + nodes.add(newNode("node" + (i + 1), Collections.singleton(roles[i]))); + } + return ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .nodes(nodes) + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); + } + private DiscoveryNode newNode(String nodeId, Set roles) { return DiscoveryNodeUtils.builder(nodeId).roles(roles).build(); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStepTests.java index 592d259f07069..879ffb3de39a6 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkShardsAllocatedStepTests.java @@ -8,8 +8,9 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; @@ -70,8 +71,7 @@ public void testConditionMet() { .numberOfShards(shrinkNumberOfShards) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) + ProjectMetadata project = ProjectMetadata.builder(randomProjectIdOrDefault()) .put(IndexMetadata.builder(originalIndexMetadata)) .put(IndexMetadata.builder(shrunkIndexMetadata)) .build(); @@ -87,13 +87,14 @@ public void testConditionMet() { for (int i = 0; i < shrinkNumberOfShards; i++) { builder.addShard(TestShardRouting.newShardRouting(new ShardId(shrinkIndex, i), nodeId, true, ShardRoutingState.STARTED)); } - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(metadata) + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) - .routingTable(RoutingTable.builder().add(builder.build()).build()) - .build(); + .putRoutingTable(project.id(), RoutingTable.builder().add(builder.build()).build()) + .build() + .projectState(project.id()); - Result result = step.isConditionMet(originalIndexMetadata.getIndex(), clusterState); + Result result = step.isConditionMet(originalIndexMetadata.getIndex(), state); assertTrue(result.complete()); assertNull(result.informationContext()); } @@ -113,8 +114,7 @@ public void testConditionNotMetBecauseOfActive() { .numberOfShards(shrinkNumberOfShards) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) + ProjectMetadata project = ProjectMetadata.builder(randomProjectIdOrDefault()) .put(IndexMetadata.builder(originalIndexMetadata)) .put(IndexMetadata.builder(shrunkIndexMetadata)) .build(); @@ -130,13 +130,14 @@ public void testConditionNotMetBecauseOfActive() { for (int i = 0; i < shrinkNumberOfShards; i++) { builder.addShard(TestShardRouting.newShardRouting(new ShardId(shrinkIndex, i), nodeId, true, ShardRoutingState.INITIALIZING)); } - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(metadata) + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) - .routingTable(RoutingTable.builder().add(builder.build()).build()) - .build(); + .putRoutingTable(project.id(), RoutingTable.builder().add(builder.build()).build()) + .build() + .projectState(project.id()); - Result result = step.isConditionMet(originalIndexMetadata.getIndex(), clusterState); + Result result = step.isConditionMet(originalIndexMetadata.getIndex(), state); assertFalse(result.complete()); assertEquals(new ShrunkShardsAllocatedStep.Info(true, shrinkNumberOfShards, false), result.informationContext()); } @@ -150,8 +151,7 @@ public void testConditionNotMetBecauseOfShrunkIndexDoesntExistYet() { .numberOfShards(originalNumberOfShards) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) + ProjectMetadata project = ProjectMetadata.builder(randomProjectIdOrDefault()) .put(IndexMetadata.builder(originalIndexMetadata)) .build(); @@ -160,12 +160,13 @@ public void testConditionNotMetBecauseOfShrunkIndexDoesntExistYet() { .applySettings(NodeRoles.masterNode(settings(IndexVersion.current()).build())) .address(new TransportAddress(TransportAddress.META_ADDRESS, 9300)) .build(); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(metadata) + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) .nodes(DiscoveryNodes.builder().localNodeId(nodeId).masterNodeId(nodeId).add(masterNode).build()) - .build(); + .build() + .projectState(project.id()); - Result result = step.isConditionMet(originalIndexMetadata.getIndex(), clusterState); + Result result = step.isConditionMet(originalIndexMetadata.getIndex(), state); assertFalse(result.complete()); assertEquals(new ShrunkShardsAllocatedStep.Info(false, -1, false), result.informationContext()); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStepTests.java index 4eb49df7f89c5..e7e576031b505 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/ShrunkenIndexCheckStepTests.java @@ -6,10 +6,9 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.xpack.core.ilm.ClusterStateWaitStep.Result; import org.elasticsearch.xpack.core.ilm.Step.StepKey; @@ -52,13 +51,9 @@ public void testConditionMet() { .numberOfShards(1) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) - .put(IndexMetadata.builder(indexMetadata)) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, false)); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metadata(metadata).build(); - Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertTrue(result.complete()); assertNull(result.informationContext()); } @@ -71,12 +66,8 @@ public void testConditionNotMetBecauseNotSameShrunkenIndex() { .numberOfShards(1) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) - .put(IndexMetadata.builder(shrinkIndexMetadata)) - .build(); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metadata(metadata).build(); - Result result = step.isConditionMet(shrinkIndexMetadata.getIndex(), clusterState); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(shrinkIndexMetadata, false)); + Result result = step.isConditionMet(shrinkIndexMetadata.getIndex(), state); assertFalse(result.complete()); assertEquals(new ShrunkenIndexCheckStep.Info(sourceIndex), result.informationContext()); } @@ -94,13 +85,10 @@ public void testConditionNotMetBecauseSourceIndexExists() { .numberOfShards(1) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) - .put(IndexMetadata.builder(originalIndexMetadata)) - .put(IndexMetadata.builder(shrinkIndexMetadata)) - .build(); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metadata(metadata).build(); - Result result = step.isConditionMet(shrinkIndexMetadata.getIndex(), clusterState); + ProjectState state = projectStateFromProject( + ProjectMetadata.builder(randomUniqueProjectId()).put(originalIndexMetadata, false).put(shrinkIndexMetadata, false) + ); + Result result = step.isConditionMet(shrinkIndexMetadata.getIndex(), state); assertFalse(result.complete()); assertEquals(new ShrunkenIndexCheckStep.Info(sourceIndex), result.informationContext()); } @@ -112,14 +100,10 @@ public void testIllegalState() { .numberOfShards(1) .numberOfReplicas(0) .build(); - Metadata metadata = Metadata.builder() - .persistentSettings(settings(IndexVersion.current()).build()) - .put(IndexMetadata.builder(indexMetadata)) - .build(); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metadata(metadata).build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, false)); IllegalStateException exception = expectThrows( IllegalStateException.class, - () -> step.isConditionMet(indexMetadata.getIndex(), clusterState) + () -> step.isConditionMet(indexMetadata.getIndex(), state) ); assertThat( exception.getMessage(), diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsTests.java index 328698254dc76..60454d8294e9e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForActiveShardsTests.java @@ -8,11 +8,12 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.AliasMetadata; import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.cluster.metadata.DataStreamTestHelper; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.ShardRoutingRoleStrategy; @@ -70,12 +71,10 @@ public void testIsConditionMetThrowsExceptionWhenRolloverAliasIsNotSet() { .numberOfShards(randomIntBetween(1, 5)) .numberOfReplicas(randomIntBetween(0, 5)) .build(); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomUniqueProjectId()).put(indexMetadata, false)); try { - createRandomInstance().isConditionMet(indexMetadata.getIndex(), clusterState); + createRandomInstance().isConditionMet(indexMetadata.getIndex(), state); fail("expected the invocation to fail"); } catch (IllegalStateException e) { assertThat( @@ -118,14 +117,16 @@ public void testResultEvaluatedOnWriteIndexAliasWhenExists() { routingTable.addShard( TestShardRouting.newShardRouting(rolledIndex.getIndex().getName(), 0, "node2", null, false, ShardRoutingState.STARTED) ); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(Metadata.builder().put(originalIndex, true).put(rolledIndex, true).build()) - .routingTable(RoutingTable.builder().add(routingTable.build()).build()) - .build(); + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(originalIndex, false).put(rolledIndex, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(routingTable.build()).build()) + .build() + .projectState(project.id()); assertThat( "the rolled index has both the primary and the replica shards started so the condition should be met", - createRandomInstance().isConditionMet(originalIndex.getIndex(), clusterState).complete(), + createRandomInstance().isConditionMet(originalIndex.getIndex(), state).complete(), is(true) ); } @@ -156,14 +157,16 @@ public void testResultEvaluatedOnOnlyIndexTheAliasPointsToIfWriteIndexIsNull() { routingTable.addShard( TestShardRouting.newShardRouting(rolledIndex.getIndex().getName(), 0, "node2", null, false, ShardRoutingState.STARTED) ); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(Metadata.builder().put(originalIndex, true).put(rolledIndex, true).build()) - .routingTable(RoutingTable.builder().add(routingTable.build()).build()) - .build(); + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(originalIndex, false).put(rolledIndex, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(routingTable.build()).build()) + .build() + .projectState(project.id()); assertThat( "the index the alias is pointing to has both the primary and the replica shards started so the condition should be" + " met", - createRandomInstance().isConditionMet(originalIndex.getIndex(), clusterState).complete(), + createRandomInstance().isConditionMet(originalIndex.getIndex(), state).complete(), is(true) ); } @@ -221,29 +224,30 @@ public void testResultEvaluatedOnDataStream() throws IOException { ) ); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata( - Metadata.builder() - .put( - DataStreamTestHelper.newInstance( - dataStreamName, - List.of(originalIndexMeta.getIndex(), rolledIndexMeta.getIndex()), - List.of(failureOriginalIndexMeta.getIndex(), failureRolledIndexMeta.getIndex()) - ) - ) - .put(originalIndexMeta, true) - .put(rolledIndexMeta, true) - .put(failureOriginalIndexMeta, true) - .put(failureRolledIndexMeta, true) + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()) + .put( + DataStreamTestHelper.newInstance( + dataStreamName, + List.of(originalIndexMeta.getIndex(), rolledIndexMeta.getIndex()), + List.of(failureOriginalIndexMeta.getIndex(), failureRolledIndexMeta.getIndex()) + ) ) - .routingTable(RoutingTable.builder().add(routingTable.build()).add(failureRoutingTable.build()).build()) + .put(originalIndexMeta, true) + .put(rolledIndexMeta, true) + .put(failureOriginalIndexMeta, true) + .put(failureRolledIndexMeta, true) .build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(routingTable.build()).add(failureRoutingTable.build()).build()) + .build() + .projectState(project.id()); WaitForActiveShardsStep waitForActiveShardsStep = createRandomInstance(); boolean useFailureStore = randomBoolean(); IndexMetadata indexToOperateOn = useFailureStore ? failureOriginalIndexMeta : originalIndexMeta; - ClusterStateWaitStep.Result result = waitForActiveShardsStep.isConditionMet(indexToOperateOn.getIndex(), clusterState); + ClusterStateWaitStep.Result result = waitForActiveShardsStep.isConditionMet(indexToOperateOn.getIndex(), state); assertThat(result.complete(), is(false)); XContentBuilder expected = new WaitForActiveShardsStep.ActiveShardsInfo(2, "3", false).toXContent( @@ -282,12 +286,14 @@ public void testResultReportsMeaningfulMessage() throws IOException { routingTable.addShard( TestShardRouting.newShardRouting(rolledIndex.getIndex().getName(), 0, "node2", null, false, ShardRoutingState.STARTED) ); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(Metadata.builder().put(originalIndex, true).put(rolledIndex, true).build()) - .routingTable(RoutingTable.builder().add(routingTable.build()).build()) - .build(); + var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(originalIndex, false).put(rolledIndex, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(routingTable.build()).build()) + .build() + .projectState(project.id()); - ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(originalIndex.getIndex(), clusterState); + ClusterStateWaitStep.Result result = createRandomInstance().isConditionMet(originalIndex.getIndex(), state); assertThat(result.complete(), is(false)); XContentBuilder expected = new WaitForActiveShardsStep.ActiveShardsInfo(2, "3", false).toXContent( @@ -310,12 +316,10 @@ public void testResultReportsErrorMessage() { .numberOfShards(1) .numberOfReplicas(2) .build(); - ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT) - .metadata(Metadata.builder().put(rolledIndex, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(rolledIndex, false)); WaitForActiveShardsStep step = createRandomInstance(); - ClusterStateWaitStep.Result result = step.isConditionMet(new Index("index-000000", UUID.randomUUID().toString()), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(new Index("index-000000", UUID.randomUUID().toString()), state); assertThat(result.complete(), is(false)); String actualResultAsString = Strings.toString(result.informationContext()); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStepTests.java index 2635e14b52eb4..cfd41035a0454 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForDataTierStepTests.java @@ -9,6 +9,8 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; @@ -69,13 +71,13 @@ public void testConditionMet() { String tierPreference = String.join(",", includedTiers); WaitForDataTierStep step = new WaitForDataTierStep(randomStepKey(), randomStepKey(), tierPreference); - verify(step, ClusterState.EMPTY_STATE, false, "no nodes for tiers [" + tierPreference + "] available"); + verify(step, projectStateWithEmptyProject(), false, "no nodes for tiers [" + tierPreference + "] available"); verify(step, state(List.of(notIncludedTier)), false, "no nodes for tiers [" + tierPreference + "] available"); verify(step, state(includedTiers), true, null); verify(step, state(List.of(DiscoveryNodeRole.DATA_ROLE.roleName())), true, null); } - private void verify(WaitForDataTierStep step, ClusterState state, boolean complete, String message) { + private void verify(WaitForDataTierStep step, ProjectState state, boolean complete, String message) { ClusterStateWaitStep.Result result = step.isConditionMet(null, state); assertThat(result.complete(), is(complete)); if (message != null) { @@ -85,7 +87,7 @@ private void verify(WaitForDataTierStep step, ClusterState state, boolean comple } } - private ClusterState state(Collection roles) { + private ProjectState state(Collection roles) { DiscoveryNodes.Builder builder = DiscoveryNodes.builder(); IntStream.range(0, between(1, 5)) .mapToObj( @@ -99,6 +101,7 @@ private ClusterState state(Collection roles) { .build() ) .forEach(builder::add); - return ClusterState.builder(ClusterName.DEFAULT).nodes(builder).build(); + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).build(); + return ClusterState.builder(ClusterName.DEFAULT).nodes(builder).putProjectMetadata(project).build().projectState(project.id()); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStepTests.java index 1414788f3ff98..dfa53f032cf30 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexColorStepTests.java @@ -9,10 +9,11 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.routing.ShardRouting; @@ -86,13 +87,15 @@ public void testConditionMetForGreen() { ); IndexRoutingTable indexRoutingTable = IndexRoutingTable.builder(indexMetadata.getIndex()).addShard(shardRouting).build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.GREEN); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); } @@ -112,13 +115,15 @@ public void testConditionNotMetForGreen() { ); IndexRoutingTable indexRoutingTable = IndexRoutingTable.builder(indexMetadata.getIndex()).addShard(shardRouting).build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.GREEN); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat(info, notNullValue()); @@ -132,13 +137,10 @@ public void testConditionNotMetNoIndexRoutingTable() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .routingTable(RoutingTable.builder().build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.YELLOW); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat(info, notNullValue()); @@ -160,13 +162,15 @@ public void testConditionMetForYellow() { ); IndexRoutingTable indexRoutingTable = IndexRoutingTable.builder(indexMetadata.getIndex()).addShard(shardRouting).build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.YELLOW); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); } @@ -186,13 +190,15 @@ public void testConditionNotMetForYellow() { ); IndexRoutingTable indexRoutingTable = IndexRoutingTable.builder(indexMetadata.getIndex()).addShard(shardRouting).build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.YELLOW); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat(info, notNullValue()); @@ -206,13 +212,10 @@ public void testConditionNotMetNoIndexRoutingTableForYellow() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .routingTable(RoutingTable.builder().build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.YELLOW); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat(info, notNullValue()); @@ -236,13 +239,15 @@ public void testStepReturnsFalseIfTargetIndexIsMissing() { ); IndexRoutingTable indexRoutingTable = IndexRoutingTable.builder(originalIndex.getIndex()).addShard(shardRouting).build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(originalIndex, true).build()) - .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) - .build(); + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()).put(originalIndex, false).build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(indexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.GREEN, indexPrefix); - ClusterStateWaitStep.Result result = step.isConditionMet(originalIndex.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(originalIndex.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); String targetIndex = indexPrefix + originalIndex.getIndex().getName(); @@ -296,13 +301,18 @@ public void testStepWaitsForTargetIndexHealthWhenPrefixConfigured() { .addShard(targetShardRouting) .build(); - ClusterState clusterTargetInitializing = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(originalIndex, true).put(targetIndex, true).build()) - .routingTable(RoutingTable.builder().add(originalIndexRoutingTable).add(targetIndexRoutingTable).build()) + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()) + .put(originalIndex, false) + .put(targetIndex, false) .build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(originalIndexRoutingTable).add(targetIndexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.GREEN); - ClusterStateWaitStep.Result result = step.isConditionMet(originalIndex.getIndex(), clusterTargetInitializing); + ClusterStateWaitStep.Result result = step.isConditionMet(originalIndex.getIndex(), state); assertThat(result.complete(), is(false)); SingleMessageFieldInfo info = (SingleMessageFieldInfo) result.informationContext(); assertThat(info.message(), is("index is not green; not all shards are active")); @@ -319,13 +329,18 @@ public void testStepWaitsForTargetIndexHealthWhenPrefixConfigured() { .addShard(targetShardRouting) .build(); - ClusterState clusterTargetInitializing = ClusterState.builder(new ClusterName("_name")) - .metadata(Metadata.builder().put(originalIndex, true).put(targetIndex, true).build()) - .routingTable(RoutingTable.builder().add(originalIndexRoutingTable).add(targetIndexRoutingTable).build()) + final var project = ProjectMetadata.builder(randomProjectIdOrDefault()) + .put(originalIndex, false) + .put(targetIndex, false) .build(); + ProjectState state = ClusterState.builder(ClusterName.DEFAULT) + .putProjectMetadata(project) + .putRoutingTable(project.id(), RoutingTable.builder().add(originalIndexRoutingTable).add(targetIndexRoutingTable).build()) + .build() + .projectState(project.id()); WaitForIndexColorStep step = new WaitForIndexColorStep(randomStepKey(), randomStepKey(), ClusterHealthStatus.GREEN); - ClusterStateWaitStep.Result result = step.isConditionMet(originalIndex.getIndex(), clusterTargetInitializing); + ClusterStateWaitStep.Result result = step.isConditionMet(originalIndex.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStepTests.java index a0982e72b11af..e14cc1d18c868 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/WaitForIndexingCompleteStepTests.java @@ -6,10 +6,9 @@ */ package org.elasticsearch.xpack.core.ilm; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexVersion; @@ -59,12 +58,10 @@ public void testConditionMet() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); WaitForIndexingCompleteStep step = createRandomInstance(); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); } @@ -76,12 +73,10 @@ public void testConditionMetNotAFollowerIndex() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); WaitForIndexingCompleteStep step = createRandomInstance(); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(true)); assertThat(result.informationContext(), nullValue()); } @@ -98,12 +93,10 @@ public void testConditionNotMet() { .numberOfReplicas(0) .build(); - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")) - .metadata(Metadata.builder().put(indexMetadata, true).build()) - .build(); + ProjectState state = projectStateFromProject(ProjectMetadata.builder(randomProjectIdOrDefault()).put(indexMetadata, false)); WaitForIndexingCompleteStep step = createRandomInstance(); - ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(indexMetadata.getIndex(), state); assertThat(result.complete(), is(false)); assertThat(result.informationContext(), notNullValue()); WaitForIndexingCompleteStep.IndexingNotCompleteInfo info = (WaitForIndexingCompleteStep.IndexingNotCompleteInfo) result @@ -118,10 +111,10 @@ public void testConditionNotMet() { } public void testIndexDeleted() { - ClusterState clusterState = ClusterState.builder(new ClusterName("cluster")).metadata(Metadata.builder().build()).build(); + ProjectState state = projectStateWithEmptyProject(); WaitForIndexingCompleteStep step = createRandomInstance(); - ClusterStateWaitStep.Result result = step.isConditionMet(new Index("this-index-doesnt-exist", "uuid"), clusterState); + ClusterStateWaitStep.Result result = step.isConditionMet(new Index("this-index-doesnt-exist", "uuid"), state); assertThat(result.complete(), is(false)); assertThat(result.informationContext(), nullValue()); } diff --git a/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/IndexLifecycleInitialisationTests.java b/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/IndexLifecycleInitialisationTests.java index aa8b2d69dbae3..cdb3716078de2 100644 --- a/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/IndexLifecycleInitialisationTests.java +++ b/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/IndexLifecycleInitialisationTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.action.support.master.AcknowledgedRequest; import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.ProjectState; import org.elasticsearch.cluster.metadata.LifecycleExecutionState; import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.common.io.stream.NamedWriteable; @@ -586,12 +587,8 @@ public String getWriteableName() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { - boolean complete = clusterState.metadata() - .getProject() - .index("test") - .getSettings() - .getAsBoolean("index.lifecycle.test.complete", false); + public Result isConditionMet(Index index, ProjectState currentState) { + boolean complete = currentState.metadata().index("test").getSettings().getAsBoolean("index.lifecycle.test.complete", false); return new Result(complete, null); } } diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/ExecuteStepsUpdateTask.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/ExecuteStepsUpdateTask.java index 29b808547b206..831f38cdceef3 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/ExecuteStepsUpdateTask.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/ExecuteStepsUpdateTask.java @@ -166,7 +166,7 @@ private ClusterState executeWaitStep(ClusterState state, Step currentStep) { currentStep.getClass().getSimpleName(), currentStep.getKey() ); - ClusterStateWaitStep.Result result = ((ClusterStateWaitStep) currentStep).isConditionMet(index, state); + ClusterStateWaitStep.Result result = ((ClusterStateWaitStep) currentStep).isConditionMet(index, state.projectState()); // some steps can decide to change the next step to execute after waiting for some time for the condition // to be met (eg. {@link LifecycleSettings#LIFECYCLE_STEP_WAIT_TIME_THRESHOLD_SETTING}, so it's important we // re-evaluate what the next step is after we evaluate the condition diff --git a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/IndexLifecycleRunnerTests.java b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/IndexLifecycleRunnerTests.java index bd39932890c69..ac4738fb787b2 100644 --- a/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/IndexLifecycleRunnerTests.java +++ b/x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/IndexLifecycleRunnerTests.java @@ -1094,7 +1094,7 @@ public long getExecuteCount() { } @Override - public Result isConditionMet(Index index, ClusterState clusterState) { + public Result isConditionMet(Index index, ProjectState currentState) { executeCount++; if (exception != null) { throw exception; From 846b09a162693f9e8b2dc671a4ad8157bd6184cc Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Sat, 7 Jun 2025 03:15:07 +1000 Subject: [PATCH 012/102] Mute org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT test {lookup-join.LookupJoinOnTimeSeriesIndex ASYNC} #129078 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 2d8240e87a6c1..bff3a2e4741c2 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -498,6 +498,9 @@ tests: - class: org.elasticsearch.packaging.test.DockerTests method: test073RunEsAsDifferentUserAndGroupWithoutBindMounting issue: https://github.com/elastic/elasticsearch/issues/128996 +- class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT + method: test {lookup-join.LookupJoinOnTimeSeriesIndex ASYNC} + issue: https://github.com/elastic/elasticsearch/issues/129078 # Examples: # From a97d58278727439f7781fd19dbe5577737507f4a Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:01:50 +0200 Subject: [PATCH 013/102] Remove `ClusterState` param from ILM `AsyncBranchingStep` (#129076) The `ClusterState` parameter of the `asyncPredicate` is not used anywhere. --- .../xpack/core/ilm/AsyncBranchingStep.java | 10 +++++----- .../org/elasticsearch/xpack/core/ilm/ShrinkAction.java | 2 +- .../xpack/core/ilm/AsyncBranchingStepTests.java | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStep.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStep.java index e45bed5872dbf..152a740312d4f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStep.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStep.java @@ -13,9 +13,9 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateObserver; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.common.TriConsumer; import java.util.Objects; +import java.util.function.BiConsumer; /** * This step changes its {@link #getNextStepKey()} depending on the @@ -26,14 +26,14 @@ public class AsyncBranchingStep extends AsyncActionStep { private final StepKey nextStepKeyOnFalse; private final StepKey nextStepKeyOnTrue; - private final TriConsumer> asyncPredicate; + private final BiConsumer> asyncPredicate; private final SetOnce predicateValue; public AsyncBranchingStep( StepKey key, StepKey nextStepKeyOnFalse, StepKey nextStepKeyOnTrue, - TriConsumer> asyncPredicate, + BiConsumer> asyncPredicate, Client client ) { // super.nextStepKey is set to null since it is not used by this step @@ -56,7 +56,7 @@ public void performAction( ClusterStateObserver observer, ActionListener listener ) { - asyncPredicate.apply(indexMetadata, currentClusterState, listener.safeMap(value -> { + asyncPredicate.accept(indexMetadata, listener.safeMap(value -> { predicateValue.set(value); return null; })); @@ -87,7 +87,7 @@ final StepKey getNextStepKeyOnTrue() { /** * @return the next step if {@code predicate} is true */ - final TriConsumer> getAsyncPredicate() { + final BiConsumer> getAsyncPredicate() { return asyncPredicate; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java index de18647773132..4988ac33b60d4 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ilm/ShrinkAction.java @@ -187,7 +187,7 @@ public List toSteps(Client client, String phase, Step.StepKey nextStepKey) preShrinkBranchingKey, checkNotWriteIndex, lastOrNextStep, - (indexMetadata, clusterState, listener) -> { + (indexMetadata, listener) -> { if (indexMetadata.getSettings().get(LifecycleSettings.SNAPSHOT_INDEX_NAME) != null) { logger.warn( "[{}] action is configured for index [{}] in policy [{}] which is mounted as searchable snapshot. " diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStepTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStepTests.java index ebef1eba0011b..1cca9fdfde3c2 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStepTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ilm/AsyncBranchingStepTests.java @@ -11,12 +11,12 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; -import org.elasticsearch.common.TriConsumer; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.xpack.core.ilm.Step.StepKey; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import static org.hamcrest.Matchers.equalTo; @@ -34,7 +34,7 @@ public void testPredicateNextStepChange() throws InterruptedException { StepKey nextStepKey = new StepKey(randomAlphaOfLength(6), randomAlphaOfLength(6), BranchingStep.NAME); StepKey nextSkipKey = new StepKey(randomAlphaOfLength(7), randomAlphaOfLength(7), BranchingStep.NAME); { - AsyncBranchingStep step = new AsyncBranchingStep(stepKey, nextStepKey, nextSkipKey, (i, c, l) -> l.onResponse(true), client); + AsyncBranchingStep step = new AsyncBranchingStep(stepKey, nextStepKey, nextSkipKey, (i, l) -> l.onResponse(true), client); expectThrows(IllegalStateException.class, step::getNextStepKey); CountDownLatch latch = new CountDownLatch(1); step.performAction(state.metadata().getProject().index(indexName), state, null, new Listener(latch)); @@ -42,7 +42,7 @@ public void testPredicateNextStepChange() throws InterruptedException { assertThat(step.getNextStepKey(), equalTo(step.getNextStepKeyOnTrue())); } { - AsyncBranchingStep step = new AsyncBranchingStep(stepKey, nextStepKey, nextSkipKey, (i, c, l) -> l.onResponse(false), client); + AsyncBranchingStep step = new AsyncBranchingStep(stepKey, nextStepKey, nextSkipKey, (i, l) -> l.onResponse(false), client); expectThrows(IllegalStateException.class, step::getNextStepKey); CountDownLatch latch = new CountDownLatch(1); step.performAction(state.metadata().getProject().index(indexName), state, null, new Listener(latch)); @@ -56,7 +56,7 @@ public AsyncBranchingStep createRandomInstance() { StepKey stepKey = new StepKey(randomAlphaOfLength(5), randomAlphaOfLength(5), BranchingStep.NAME); StepKey nextStepKey = new StepKey(randomAlphaOfLength(6), randomAlphaOfLength(6), BranchingStep.NAME); StepKey nextSkipKey = new StepKey(randomAlphaOfLength(7), randomAlphaOfLength(7), BranchingStep.NAME); - return new AsyncBranchingStep(stepKey, nextStepKey, nextSkipKey, (i, c, l) -> l.onResponse(randomBoolean()), client); + return new AsyncBranchingStep(stepKey, nextStepKey, nextSkipKey, (i, l) -> l.onResponse(randomBoolean()), client); } @Override @@ -64,7 +64,7 @@ public AsyncBranchingStep mutateInstance(AsyncBranchingStep instance) { StepKey key = instance.getKey(); StepKey nextStepKey = instance.getNextStepKeyOnFalse(); StepKey nextSkipStepKey = instance.getNextStepKeyOnTrue(); - TriConsumer> asyncPredicate = instance.getAsyncPredicate(); + BiConsumer> asyncPredicate = instance.getAsyncPredicate(); switch (between(0, 2)) { case 0 -> key = new StepKey(key.phase(), key.action(), key.name() + randomAlphaOfLength(5)); From 763b5026f4bed4b16e665d25011faa54a27da057 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Sat, 7 Jun 2025 04:03:11 +1000 Subject: [PATCH 014/102] Mute org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT test {lookup-join.LookupJoinOnTimeSeriesIndex SYNC} #129082 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index bff3a2e4741c2..1be1957894a9f 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -501,6 +501,9 @@ tests: - class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT method: test {lookup-join.LookupJoinOnTimeSeriesIndex ASYNC} issue: https://github.com/elastic/elasticsearch/issues/129078 +- class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT + method: test {lookup-join.LookupJoinOnTimeSeriesIndex SYNC} + issue: https://github.com/elastic/elasticsearch/issues/129082 # Examples: # From 8a660c8419bd1b7f2d37d87334cb1f0aed1c94e0 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Sat, 7 Jun 2025 08:07:15 +1000 Subject: [PATCH 015/102] Mute org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT test {p0=upgraded_cluster/70_ilm/Test Lifecycle Still There And Indices Are Still Managed} #129097 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 1be1957894a9f..42a8c6951195c 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -504,6 +504,9 @@ tests: - class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT method: test {lookup-join.LookupJoinOnTimeSeriesIndex SYNC} issue: https://github.com/elastic/elasticsearch/issues/129082 +- class: org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT + method: test {p0=upgraded_cluster/70_ilm/Test Lifecycle Still There And Indices Are Still Managed} + issue: https://github.com/elastic/elasticsearch/issues/129097 # Examples: # From aa1617521ba5b4081f33499e86522bc879efe7cf Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Sat, 7 Jun 2025 08:07:23 +1000 Subject: [PATCH 016/102] Mute org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT test {p0=upgraded_cluster/90_ml_data_frame_analytics_crud/Get mixed cluster outlier_detection job} #129098 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 42a8c6951195c..fbf0b5cc43244 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -507,6 +507,9 @@ tests: - class: org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT method: test {p0=upgraded_cluster/70_ilm/Test Lifecycle Still There And Indices Are Still Managed} issue: https://github.com/elastic/elasticsearch/issues/129097 +- class: org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT + method: test {p0=upgraded_cluster/90_ml_data_frame_analytics_crud/Get mixed cluster outlier_detection job} + issue: https://github.com/elastic/elasticsearch/issues/129098 # Examples: # From 6e58b1eca6ffabe6e785633ecc2535b9a60d2388 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:48:53 +1000 Subject: [PATCH 017/102] Mute org.elasticsearch.packaging.test.DockerTests test081SymlinksAreFollowedWithEnvironmentVariableFiles #128867 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index fbf0b5cc43244..4ab27070d12e2 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -510,6 +510,9 @@ tests: - class: org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT method: test {p0=upgraded_cluster/90_ml_data_frame_analytics_crud/Get mixed cluster outlier_detection job} issue: https://github.com/elastic/elasticsearch/issues/129098 +- class: org.elasticsearch.packaging.test.DockerTests + method: test081SymlinksAreFollowedWithEnvironmentVariableFiles + issue: https://github.com/elastic/elasticsearch/issues/128867 # Examples: # From 05f70f05ab9e684ab4ea065acd49120a761e14ad Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 8 Jun 2025 17:26:10 +0300 Subject: [PATCH 018/102] Threadpool merge executor is aware of available disk space (#127613) This PR introduces 3 new settings: indices.merge.disk.check_interval, indices.merge.disk.watermark.high, and indices.merge.disk.watermark.high.max_headroom that control if the threadpool merge executor starts executing new merges when the disk space is getting low. The intent of this change is to avoid the situation where in-progress merges exhaust the available disk space on the node's local filesystem. To this end, the thread pool merge executor periodically monitors the available disk space, as well as the current disk space estimates required by all in-progress (currently running) merges on the node, and will NOT schedule any new merges if the disk space is getting low (by default below the 5% limit of the total disk space, or 100 GB, whichever is smaller (same as the disk allocation flood stage level)). --- docs/changelog/127613.yaml | 5 + .../common/settings/ClusterSettings.java | 4 + .../ThreadPoolMergeExecutorService.java | 549 ++++++++- .../engine/ThreadPoolMergeScheduler.java | 30 +- .../elasticsearch/indices/IndicesService.java | 12 +- .../elasticsearch/index/IndexModuleTests.java | 8 +- ...oolMergeExecutorServiceDiskSpaceTests.java | 1023 +++++++++++++++++ .../ThreadPoolMergeExecutorServiceTests.java | 269 ++++- .../engine/ThreadPoolMergeSchedulerTests.java | 43 +- .../index/shard/RefreshListenersTests.java | 13 +- .../index/engine/EngineTestCase.java | 9 +- .../index/shard/IndexShardTestCase.java | 10 +- .../index/engine/FollowingEngineTests.java | 13 +- 13 files changed, 1891 insertions(+), 97 deletions(-) create mode 100644 docs/changelog/127613.yaml create mode 100644 server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceDiskSpaceTests.java diff --git a/docs/changelog/127613.yaml b/docs/changelog/127613.yaml new file mode 100644 index 0000000000000..de043e209b32e --- /dev/null +++ b/docs/changelog/127613.yaml @@ -0,0 +1,5 @@ +pr: 127613 +summary: Threadpool merge executor is aware of available disk space +area: Engine +type: feature +issues: [] 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 a15d4f3049528..dea61e770b2f6 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -88,6 +88,7 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexingPressure; import org.elasticsearch.index.MergePolicyConfig; +import org.elasticsearch.index.engine.ThreadPoolMergeExecutorService; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.IndexingMemoryController; @@ -629,6 +630,9 @@ public void apply(Settings value, Settings current, Settings previous) { MergePolicyConfig.DEFAULT_MAX_MERGED_SEGMENT_SETTING, MergePolicyConfig.DEFAULT_MAX_TIME_BASED_MERGED_SEGMENT_SETTING, ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING, + ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING, + ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING, + ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING, TransportService.ENABLE_STACK_OVERFLOW_AVOIDANCE, DataStreamGlobalRetentionSettings.DATA_STREAMS_DEFAULT_RETENTION_SETTING, DataStreamGlobalRetentionSettings.DATA_STREAMS_MAX_RETENTION_SETTING, diff --git a/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorService.java b/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorService.java index d3dff8c30449a..9e74c19d8a85e 100644 --- a/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorService.java +++ b/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorService.java @@ -9,29 +9,154 @@ package org.elasticsearch.index.engine; -import org.elasticsearch.common.settings.Settings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.unit.RelativeByteSizeValue; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.Releasable; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler.MergeTask; +import org.elasticsearch.monitor.fs.FsInfo; +import org.elasticsearch.threadpool.Scheduler; import org.elasticsearch.threadpool.ThreadPool; +import java.io.Closeable; +import java.io.IOException; +import java.util.Arrays; import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; -import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; import java.util.function.LongUnaryOperator; +import java.util.function.ToLongFunction; +import static org.elasticsearch.cluster.routing.allocation.DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_MAX_HEADROOM_SETTING; +import static org.elasticsearch.cluster.routing.allocation.DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_WATERMARK_SETTING; import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.ABORT; import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.BACKLOG; import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.RUN; +import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING; +import static org.elasticsearch.monitor.fs.FsProbe.getFSInfo; -public class ThreadPoolMergeExecutorService { +public class ThreadPoolMergeExecutorService implements Closeable { + /** How frequently we check disk usage (default: 5 seconds). */ + public static final Setting INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING = Setting.positiveTimeSetting( + "indices.merge.disk.check_interval", + TimeValue.timeValueSeconds(5), + Property.Dynamic, + Property.NodeScope + ); + /** + * The occupied disk space threshold beyond which NO new merges are started. + * Conservatively, the estimated temporary disk space required for the to-be-started merge is counted as occupied disk space. + * Defaults to the routing allocation flood stage limit value (beyond which shards are toggled read-only). + */ + public static final Setting INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING = new Setting<>( + "indices.merge.disk.watermark.high", + CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_WATERMARK_SETTING, + (s) -> RelativeByteSizeValue.parseRelativeByteSizeValue(s, "indices.merge.disk.watermark.high"), + new Setting.Validator<>() { + @Override + public void validate(RelativeByteSizeValue value) {} + + @Override + public void validate(RelativeByteSizeValue value, Map, Object> settings, boolean isPresent) { + if (isPresent && settings.get(USE_THREAD_POOL_MERGE_SCHEDULER_SETTING).equals(Boolean.FALSE)) { + throw new IllegalArgumentException( + "indices merge watermark setting is only effective when [" + + USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey() + + "] is set to [true]" + ); + } + } + + @Override + public Iterator> settings() { + List> res = List.of(INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING, USE_THREAD_POOL_MERGE_SCHEDULER_SETTING); + return res.iterator(); + } + }, + Property.Dynamic, + Property.NodeScope + ); + /** + * The available disk space headroom below which NO new merges are started. + * Conservatively, the estimated temporary disk space required for the to-be-started merge is NOT counted as available disk space. + * Defaults to the routing allocation flood stage headroom value (below which shards are toggled read-only), + * unless the merge occupied disk space threshold is specified, in which case the default headroom value here is unset. + */ + public static final Setting INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING = new Setting<>( + "indices.merge.disk.watermark.high.max_headroom", + (settings) -> { + // if the user explicitly set a value for the occupied disk space threshold, disable the implicit headroom value + if (INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING.exists(settings)) { + return "-1"; + } else { + return CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_MAX_HEADROOM_SETTING.get(settings).toString(); + } + }, + (s) -> ByteSizeValue.parseBytesSizeValue(s, "indices.merge.disk.watermark.high.max_headroom"), + new Setting.Validator<>() { + @Override + public void validate(ByteSizeValue value) {} + + @Override + public void validate(final ByteSizeValue value, final Map, Object> settings, boolean isPresent) { + if (isPresent) { + if (value.equals(ByteSizeValue.MINUS_ONE)) { + throw new IllegalArgumentException( + "setting a headroom value to less than 0 is not supported, use [null] value to unset" + ); + } + if (settings.get(USE_THREAD_POOL_MERGE_SCHEDULER_SETTING).equals(Boolean.FALSE)) { + throw new IllegalArgumentException( + "indices merge max headroom setting is only effective when [" + + USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey() + + "] is set to [true]" + ); + } + } + final RelativeByteSizeValue highWatermark = (RelativeByteSizeValue) settings.get(INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING); + final ByteSizeValue highHeadroom = (ByteSizeValue) settings.get(INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING); + if (highWatermark.isAbsolute() && highHeadroom.equals(ByteSizeValue.MINUS_ONE) == false) { + throw new IllegalArgumentException( + "indices merge max headroom setting is set, but indices merge disk watermark value is not a relative value [" + + highWatermark.getStringRep() + + "]" + ); + } + } + + @Override + public Iterator> settings() { + List> res = List.of( + INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING, + INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING, + USE_THREAD_POOL_MERGE_SCHEDULER_SETTING + ); + return res.iterator(); + } + }, + Property.Dynamic, + Property.NodeScope + ); /** * Floor for IO write rate limit of individual merge tasks (we will never go any lower than this) */ @@ -52,11 +177,10 @@ public class ThreadPoolMergeExecutorService { /** * The merge tasks that are waiting execution. This does NOT include backlogged or currently executing merge tasks. * For instance, this can be empty while there are backlogged merge tasks awaiting re-enqueuing. + * The budget (estimation) for a merge task is the disk space (still) required for it to complete. As the merge progresses, + * its budget decreases (as the bytes already written have been incorporated into the filesystem stats about the used disk space). */ - private final PriorityBlockingQueue queuedMergeTasks = new PriorityBlockingQueue<>( - 64, - Comparator.comparingLong(MergeTask::estimatedMergeSize) - ); + private final MergeTaskPriorityBlockingQueue queuedMergeTasks = new MergeTaskPriorityBlockingQueue(); /** * The set of all merge tasks currently being executed by merge threads from the pool. * These are tracked notably in order to be able to update their disk IO throttle rate, after they have started, while executing. @@ -74,31 +198,45 @@ public class ThreadPoolMergeExecutorService { private final int maxConcurrentMerges; private final int concurrentMergesFloorLimitForThrottling; private final int concurrentMergesCeilLimitForThrottling; + private final AvailableDiskSpacePeriodicMonitor availableDiskSpacePeriodicMonitor; private final List mergeEventListeners = new CopyOnWriteArrayList<>(); public static @Nullable ThreadPoolMergeExecutorService maybeCreateThreadPoolMergeExecutorService( ThreadPool threadPool, - Settings settings + ClusterSettings clusterSettings, + NodeEnvironment nodeEnvironment ) { - if (ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.get(settings)) { - return new ThreadPoolMergeExecutorService(threadPool); + if (clusterSettings.get(USE_THREAD_POOL_MERGE_SCHEDULER_SETTING)) { + return new ThreadPoolMergeExecutorService(threadPool, clusterSettings, nodeEnvironment); } else { + // register no-op setting update consumers so that setting validations work properly + // (some validations are bypassed if there are no update consumers registered), + // i.e. to reject watermark and max headroom updates if the thread pool merge scheduler is disabled + clusterSettings.addSettingsUpdateConsumer(INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING, (ignored) -> {}); + clusterSettings.addSettingsUpdateConsumer(INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING, (ignored) -> {}); + clusterSettings.addSettingsUpdateConsumer(INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING, (ignored) -> {}); return null; } } - private ThreadPoolMergeExecutorService(ThreadPool threadPool) { + private ThreadPoolMergeExecutorService(ThreadPool threadPool, ClusterSettings clusterSettings, NodeEnvironment nodeEnvironment) { this.executorService = threadPool.executor(ThreadPool.Names.MERGE); this.maxConcurrentMerges = threadPool.info(ThreadPool.Names.MERGE).getMax(); // the intent here is to throttle down whenever we submit a task and no other task is running this.concurrentMergesFloorLimitForThrottling = 2; this.concurrentMergesCeilLimitForThrottling = maxConcurrentMerges * 2; assert concurrentMergesFloorLimitForThrottling <= concurrentMergesCeilLimitForThrottling; + this.availableDiskSpacePeriodicMonitor = startDiskSpaceMonitoring( + threadPool, + nodeEnvironment.dataPaths(), + clusterSettings, + (availableDiskSpaceByteSize) -> this.queuedMergeTasks.updateBudget(availableDiskSpaceByteSize.getBytes()) + ); } boolean submitMergeTask(MergeTask mergeTask) { - assert mergeTask.isRunning() == false; + assert mergeTask.hasStartedRunning() == false; // first enqueue the runnable that runs exactly one merge task (the smallest it can find) if (enqueueMergeTaskExecution() == false) { // if the thread pool cannot run the merge, just abort it @@ -137,6 +275,7 @@ boolean submitMergeTask(MergeTask mergeTask) { } void reEnqueueBackloggedMergeTask(MergeTask mergeTask) { + assert mergeTask.hasStartedRunning() == false; enqueueMergeTask(mergeTask); } @@ -144,12 +283,12 @@ private void enqueueMergeTask(MergeTask mergeTask) { // To ensure that for a given merge onMergeQueued is called before onMergeAborted or onMergeCompleted, we call onMergeQueued // before adding the merge task to the queue. Adding to the queue should not fail. mergeEventListeners.forEach(l -> l.onMergeQueued(mergeTask.getOnGoingMerge(), mergeTask.getMergeMemoryEstimateBytes())); - boolean added = queuedMergeTasks.add(mergeTask); + boolean added = queuedMergeTasks.enqueue(mergeTask); assert added; } public boolean allDone() { - return queuedMergeTasks.isEmpty() && runningMergeTasks.isEmpty() && ioThrottledMergeTasksCount.get() == 0L; + return queuedMergeTasks.isQueueEmpty() && runningMergeTasks.isEmpty() && ioThrottledMergeTasksCount.get() == 0L; } /** @@ -162,10 +301,13 @@ private boolean enqueueMergeTaskExecution() { // one such runnable always executes a SINGLE merge task from the queue // this is important for merge queue statistics, i.e. the executor's queue size represents the current amount of merges while (true) { - MergeTask smallestMergeTask; + PriorityBlockingQueueWithBudget.ElementWithReleasableBudget smallestMergeTaskWithReleasableBudget; try { - // will block if there are backlogged merges until they're enqueued again - smallestMergeTask = queuedMergeTasks.take(); + // Will block if there are backlogged merges until they're enqueued again + // (for e.g. if the per-shard concurrent merges count limit is reached). + // Will also block if there is insufficient budget (i.e. estimated available disk space + // for the smallest merge task to run to completion) + smallestMergeTaskWithReleasableBudget = queuedMergeTasks.take(); } catch (InterruptedException e) { // An active worker thread has been interrupted while waiting for backlogged merges to be re-enqueued. // In this case, we terminate the worker thread promptly and forget about the backlogged merges. @@ -175,18 +317,24 @@ private boolean enqueueMergeTaskExecution() { // is also drained, so any queued merge tasks are also forgotten. break; } - // let the task's scheduler decide if it can actually run the merge task now - ThreadPoolMergeScheduler.Schedule schedule = smallestMergeTask.schedule(); - if (schedule == RUN) { - runMergeTask(smallestMergeTask); - break; - } else if (schedule == ABORT) { - abortMergeTask(smallestMergeTask); - break; - } else { - assert schedule == BACKLOG; - // the merge task is backlogged by the merge scheduler, try to get the next smallest one - // it's then the duty of the said merge scheduler to re-enqueue the backlogged merge task when it can be run + try (var ignored = smallestMergeTaskWithReleasableBudget) { + MergeTask smallestMergeTask = smallestMergeTaskWithReleasableBudget.element(); + // let the task's scheduler decide if it can actually run the merge task now + ThreadPoolMergeScheduler.Schedule schedule = smallestMergeTask.schedule(); + if (schedule == RUN) { + runMergeTask(smallestMergeTask); + break; + } else if (schedule == ABORT) { + abortMergeTask(smallestMergeTask); + break; + } else { + assert schedule == BACKLOG; + // The merge task is backlogged by the merge scheduler, try to get the next smallest one. + // It's then the duty of the said merge scheduler to re-enqueue the backlogged merge task when + // itself decides that the merge task could be run. Note that it is possible that this merge + // task is re-enqueued and re-took before the budget hold-up here is released upon the next + // {@link PriorityBlockingQueueWithBudget#updateBudget} invocation. + } } } }); @@ -199,7 +347,7 @@ private boolean enqueueMergeTaskExecution() { } private void runMergeTask(MergeTask mergeTask) { - assert mergeTask.isRunning() == false; + assert mergeTask.hasStartedRunning() == false; boolean added = runningMergeTasks.add(mergeTask); assert added : "starting merge task [" + mergeTask + "] registered as already running"; try { @@ -218,7 +366,7 @@ private void runMergeTask(MergeTask mergeTask) { } private void abortMergeTask(MergeTask mergeTask) { - assert mergeTask.isRunning() == false; + assert mergeTask.hasStartedRunning() == false; assert runningMergeTasks.contains(mergeTask) == false; try { mergeTask.abort(); @@ -230,6 +378,331 @@ private void abortMergeTask(MergeTask mergeTask) { } } + /** + * Start monitoring the available disk space, and update the available budget for running merge tasks + * Note: this doesn't work correctly for nodes with multiple data paths, as it only considers the data path with the MOST + * available disk space. In this case, merges will NOT be blocked for shards on data paths with insufficient available + * disk space, as long as a single data path has enough available disk space to run merges for any shards that it stores + * (i.e. multiple data path is not really supported when blocking merges due to insufficient available disk space + * (but nothing blows up either, if using multiple data paths)) + */ + static AvailableDiskSpacePeriodicMonitor startDiskSpaceMonitoring( + ThreadPool threadPool, + NodeEnvironment.DataPath[] dataPaths, + ClusterSettings clusterSettings, + Consumer availableDiskSpaceUpdateConsumer + ) { + AvailableDiskSpacePeriodicMonitor availableDiskSpacePeriodicMonitor = new AvailableDiskSpacePeriodicMonitor( + dataPaths, + threadPool, + clusterSettings.get(INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING), + clusterSettings.get(INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING), + clusterSettings.get(INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING), + availableDiskSpaceByteSize -> { + if (availableDiskSpaceByteSize.equals(ByteSizeValue.MINUS_ONE)) { + // The merge executor is currently unaware of the available disk space because of an error. + // Merges are NOT blocked if the available disk space is insufficient. + availableDiskSpaceUpdateConsumer.accept(ByteSizeValue.ofBytes(Long.MAX_VALUE)); + } else { + availableDiskSpaceUpdateConsumer.accept(availableDiskSpaceByteSize); + } + } + ); + if (availableDiskSpacePeriodicMonitor.isScheduled() == false) { + // in case the disk space monitor starts off as disabled, then make sure that merging is NOT blocked + // (in the other case, merging IS blocked until the first update for the available disk space) + availableDiskSpaceUpdateConsumer.accept(ByteSizeValue.ofBytes(Long.MAX_VALUE)); + } + clusterSettings.addSettingsUpdateConsumer( + INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING, + availableDiskSpacePeriodicMonitor::setHighStageWatermark + ); + clusterSettings.addSettingsUpdateConsumer( + INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING, + availableDiskSpacePeriodicMonitor::setHighStageMaxHeadroom + ); + clusterSettings.addSettingsUpdateConsumer( + INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING, + availableDiskSpacePeriodicMonitor::setCheckInterval + ); + return availableDiskSpacePeriodicMonitor; + } + + static class AvailableDiskSpacePeriodicMonitor implements Closeable { + private static final Logger LOGGER = LogManager.getLogger(AvailableDiskSpacePeriodicMonitor.class); + private final NodeEnvironment.DataPath[] dataPaths; + private final ThreadPool threadPool; + private volatile RelativeByteSizeValue highStageWatermark; + private volatile ByteSizeValue highStageMaxHeadroom; + private volatile TimeValue checkInterval; + private final Consumer updateConsumer; + private volatile boolean closed; + private volatile Scheduler.Cancellable monitor; + + AvailableDiskSpacePeriodicMonitor( + NodeEnvironment.DataPath[] dataPaths, + ThreadPool threadPool, + RelativeByteSizeValue highStageWatermark, + ByteSizeValue highStageMaxHeadroom, + TimeValue checkInterval, + Consumer updateConsumer + ) { + this.dataPaths = dataPaths; + this.threadPool = threadPool; + this.highStageWatermark = highStageWatermark; + this.highStageMaxHeadroom = highStageMaxHeadroom; + this.checkInterval = checkInterval; + this.updateConsumer = updateConsumer; + this.closed = false; + reschedule(); + } + + void setCheckInterval(TimeValue checkInterval) { + this.checkInterval = checkInterval; + reschedule(); + } + + void setHighStageWatermark(RelativeByteSizeValue highStageWatermark) { + this.highStageWatermark = highStageWatermark; + } + + void setHighStageMaxHeadroom(ByteSizeValue highStageMaxHeadroom) { + this.highStageMaxHeadroom = highStageMaxHeadroom; + } + + private synchronized void reschedule() { + if (monitor != null) { + monitor.cancel(); + } + if (closed == false && checkInterval.duration() > 0) { + // do an eager run, + // in order to increase responsiveness in case the period is long and something blocks waiting for the first update + threadPool.generic().execute(this::run); + monitor = threadPool.scheduleWithFixedDelay(this::run, checkInterval, threadPool.generic()); + } else { + monitor = null; + } + } + + boolean isScheduled() { + return monitor != null && closed == false; + } + + @Override + public void close() throws IOException { + closed = true; + reschedule(); + } + + private void run() { + if (closed) { + return; + } + FsInfo.Path mostAvailablePath = null; + IOException fsInfoException = null; + for (NodeEnvironment.DataPath dataPath : dataPaths) { + try { + FsInfo.Path fsInfo = getFSInfo(dataPath); // uncached + if (mostAvailablePath == null || mostAvailablePath.getAvailable().getBytes() < fsInfo.getAvailable().getBytes()) { + mostAvailablePath = fsInfo; + } + } catch (IOException e) { + if (fsInfoException == null) { + fsInfoException = e; + } else { + fsInfoException.addSuppressed(e); + } + } + } + if (fsInfoException != null) { + LOGGER.warn("unexpected exception reading filesystem info", fsInfoException); + } + if (mostAvailablePath == null) { + LOGGER.error("Cannot read filesystem info for node data paths " + Arrays.toString(dataPaths)); + updateConsumer.accept(ByteSizeValue.MINUS_ONE); + return; + } + long mostAvailableDiskSpaceBytes = mostAvailablePath.getAvailable().getBytes(); + // subtract the configured free disk space threshold + mostAvailableDiskSpaceBytes -= getFreeBytesThreshold(mostAvailablePath.getTotal(), highStageWatermark, highStageMaxHeadroom) + .getBytes(); + // clamp available space to 0 + long maxMergeSizeLimit = Math.max(0L, mostAvailableDiskSpaceBytes); + updateConsumer.accept(ByteSizeValue.ofBytes(maxMergeSizeLimit)); + } + + private static ByteSizeValue getFreeBytesThreshold( + ByteSizeValue total, + RelativeByteSizeValue watermark, + ByteSizeValue maxHeadroom + ) { + // If bytes are given, they can be readily returned as free bytes. + // If percentages are given, we need to calculate the free bytes. + if (watermark.isAbsolute()) { + return watermark.getAbsolute(); + } + return ByteSizeValue.subtract(total, watermark.calculateValue(total, maxHeadroom)); + } + } + + static class MergeTaskPriorityBlockingQueue extends PriorityBlockingQueueWithBudget { + MergeTaskPriorityBlockingQueue() { + // start with 0 budget (so takes on this queue will always block until {@link #updateBudget} is invoked) + // use the estimated *remaining* merge size as the budget function so that the disk space budget of taken (in-use) elements is + // updated according to the remaining disk space requirements of the currently running merge tasks + super(MergeTask::estimatedRemainingMergeSize, 0L); + } + + // exposed for tests + long getAvailableBudget() { + return super.availableBudget; + } + + // exposed for tests + MergeTask peekQueue() { + return enqueuedByBudget.peek(); + } + } + + /** + * Similar to a regular priority queue, but the {@link #take()} operation will also block if the smallest element + * (according to the specified "budget" function) is larger than an updatable limit budget. + */ + static class PriorityBlockingQueueWithBudget { + private final ToLongFunction budgetFunction; + protected final PriorityQueue enqueuedByBudget; + private final IdentityHashMap unreleasedBudgetPerElement; + private final ReentrantLock lock; + private final Condition elementAvailable; + protected long availableBudget; + + PriorityBlockingQueueWithBudget(ToLongFunction budgetFunction, long initialAvailableBudget) { + this.budgetFunction = budgetFunction; + this.enqueuedByBudget = new PriorityQueue<>(64, Comparator.comparingLong(budgetFunction)); + this.unreleasedBudgetPerElement = new IdentityHashMap<>(); + this.lock = new ReentrantLock(); + this.elementAvailable = lock.newCondition(); + this.availableBudget = initialAvailableBudget; + } + + boolean enqueue(E e) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + enqueuedByBudget.offer(e); + elementAvailable.signal(); + } finally { + lock.unlock(); + } + return true; + } + + /** + * Dequeues the smallest element (according to the specified "budget" function) if its budget is below the available limit. + * This method invocation blocks if the queue is empty or the element's budget is above the available limit. + */ + ElementWithReleasableBudget take() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + E peek; + long peekBudget; + // blocks until the smallest budget element fits the currently available budget + while ((peek = enqueuedByBudget.peek()) == null || (peekBudget = budgetFunction.applyAsLong(peek)) > availableBudget) { + elementAvailable.await(); + } + // deducts and holds up that element's budget from the available budget + return newElementWithReleasableBudget(enqueuedByBudget.poll(), peekBudget); + } finally { + lock.unlock(); + } + } + + /** + * Updates the available budged given the passed-in argument, from which it deducts the budget hold up by taken elements + * that are still in use. The budget of in-use elements is also updated (by re-applying the budget function). + * The newly updated budget is used to potentially block {@link #take()} operations if the smallest-budget enqueued element + * is over this newly computed available budget. + */ + void updateBudget(long availableBudget) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + this.availableBudget = availableBudget; + // update the per-element budget (these are all the elements that are using any budget) + unreleasedBudgetPerElement.replaceAll((e, v) -> budgetFunction.applyAsLong(e.element())); + // available budget is decreased by the used per-element budget (for all dequeued elements that are still in use) + this.availableBudget -= unreleasedBudgetPerElement.values().stream().mapToLong(i -> i).sum(); + elementAvailable.signalAll(); + } finally { + lock.unlock(); + } + } + + boolean isQueueEmpty() { + return enqueuedByBudget.isEmpty(); + } + + int queueSize() { + return enqueuedByBudget.size(); + } + + private ElementWithReleasableBudget newElementWithReleasableBudget(E element, long budget) { + ElementWithReleasableBudget elementWithReleasableBudget = new ElementWithReleasableBudget(element); + assert this.lock.isHeldByCurrentThread(); + // the taken element holds up some budget + var prev = this.unreleasedBudgetPerElement.put(elementWithReleasableBudget, budget); + assert prev == null; + this.availableBudget -= budget; + assert this.availableBudget >= 0L; + return elementWithReleasableBudget; + } + + private void release(ElementWithReleasableBudget elementWithReleasableBudget) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + assert elementWithReleasableBudget.isClosed() == false; + // when the taken element is not used anymore, it will not influence subsequent computations for available budget, + // but its allotted budget is not yet released + var val = unreleasedBudgetPerElement.remove(elementWithReleasableBudget); + assert val != null; + } finally { + lock.unlock(); + } + } + + private boolean isReleased(ElementWithReleasableBudget elementWithReleasableBudget) { + return unreleasedBudgetPerElement.containsKey(elementWithReleasableBudget) == false; + } + + class ElementWithReleasableBudget implements Releasable { + private final E element; + + private ElementWithReleasableBudget(E element) { + this.element = element; + } + + /** + * Must be invoked when the caller is done with the element that it previously took from the queue. + * The budget it's holding is not immediately released, but the next time {@link #updateBudget(long)} + * is invoked this element's budget won't deduct from the total available. + */ + @Override + public void close() { + PriorityBlockingQueueWithBudget.this.release(this); + } + + boolean isClosed() { + return PriorityBlockingQueueWithBudget.this.isReleased(this); + } + + E element() { + return element; + } + } + } + private static long newTargetIORateBytesPerSec( long currentTargetIORateBytesPerSec, int currentlySubmittedIOThrottledMergeTasks, @@ -302,8 +775,13 @@ Set getRunningMergeTasks() { } // exposed for tests - PriorityBlockingQueue getQueuedMergeTasks() { - return queuedMergeTasks; + int getMergeTasksQueueLength() { + return queuedMergeTasks.queueSize(); + } + + // exposed for tests + long getDiskSpaceAvailableForNewMergeTasks() { + return queuedMergeTasks.getAvailableBudget(); } // exposed for tests and stats @@ -315,4 +793,9 @@ long getTargetIORateBytesPerSec() { int getMaxConcurrentMerges() { return maxConcurrentMerges; } + + @Override + public void close() throws IOException { + availableDiskSpacePeriodicMonitor.close(); + } } diff --git a/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java b/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java index 33ef06699c8c7..78a9695bea540 100644 --- a/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java +++ b/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java @@ -55,7 +55,7 @@ public class ThreadPoolMergeScheduler extends MergeScheduler implements Elastics private final ThreadPoolMergeExecutorService threadPoolMergeExecutorService; private final PriorityQueue backloggedMergeTasks = new PriorityQueue<>( 16, - Comparator.comparingLong(MergeTask::estimatedMergeSize) + Comparator.comparingLong(MergeTask::estimatedRemainingMergeSize) ); private final Map runningMergeTasks = new HashMap<>(); // set when incoming merges should be throttled (i.e. restrict the indexing rate) @@ -266,7 +266,7 @@ private void checkMergeTaskThrottling() { // exposed for tests // synchronized so that {@code #closed}, {@code #runningMergeTasks} and {@code #backloggedMergeTasks} are modified atomically synchronized Schedule schedule(MergeTask mergeTask) { - assert mergeTask.isRunning() == false; + assert mergeTask.hasStartedRunning() == false; if (closed) { // do not run or backlog tasks when closing the merge scheduler, instead abort them return Schedule.ABORT; @@ -280,6 +280,7 @@ synchronized Schedule schedule(MergeTask mergeTask) { assert added : "starting merge task [" + mergeTask + "] registered as already running"; return Schedule.RUN; } else { + assert mergeTask.hasStartedRunning() == false; backloggedMergeTasks.add(mergeTask); return Schedule.BACKLOG; } @@ -403,8 +404,14 @@ public void setIORateLimit(long ioRateLimitBytesPerSec) { this.rateLimiter.setMBPerSec(ByteSizeValue.ofBytes(ioRateLimitBytesPerSec).getMbFrac()); } - public boolean isRunning() { - return mergeStartTimeNS.get() > 0L; + /** + * Returns {@code true} if this task is currently running, or was run in the past. + * An aborted task (see {@link #abort()}) is considered as NOT run. + */ + public boolean hasStartedRunning() { + boolean isRunning = mergeStartTimeNS.get() > 0L; + assert isRunning != false || rateLimiter.getTotalBytesWritten() == 0L; + return isRunning; } /** @@ -415,7 +422,7 @@ public boolean isRunning() { */ @Override public void run() { - assert isRunning() == false; + assert hasStartedRunning() == false; assert ThreadPoolMergeScheduler.this.runningMergeTasks.containsKey(onGoingMerge.getMerge()) : "runNowOrBacklog must be invoked before actually running the merge task"; try { @@ -480,7 +487,7 @@ public void run() { * (by the {@link org.apache.lucene.index.IndexWriter}) to any subsequent merges. */ void abort() { - assert isRunning() == false; + assert hasStartedRunning() == false; assert ThreadPoolMergeScheduler.this.runningMergeTasks.containsKey(onGoingMerge.getMerge()) == false : "cannot abort a merge task that's already running"; if (verbose()) { @@ -509,10 +516,17 @@ void abort() { } } - long estimatedMergeSize() { + /** + * Before the merge task started running, this returns the estimated required disk space for the merge to complete + * (i.e. the estimated disk space size of the resulting segment following the merge). + * While the merge is running, the returned estimation is updated to take into account the data that's already been written. + * After the merge completes, the estimation returned here should ideally be close to "0". + */ + long estimatedRemainingMergeSize() { // TODO is it possible that `estimatedMergeBytes` be `0` for correctly initialize merges, // or is it always the case that if `estimatedMergeBytes` is `0` that means that the merge has not yet been initialized? - return onGoingMerge.getMerge().getStoreMergeInfo().estimatedMergeBytes(); + long estimatedMergeSize = onGoingMerge.getMerge().getStoreMergeInfo().estimatedMergeBytes(); + return Math.max(0L, estimatedMergeSize - rateLimiter.getTotalBytesWritten()); } public long getMergeMemoryEstimateBytes() { diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesService.java b/server/src/main/java/org/elasticsearch/indices/IndicesService.java index 73747bc798d30..7825b3513a6b3 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -295,10 +295,6 @@ protected void doStart() { IndicesService(IndicesServiceBuilder builder) { this.settings = builder.settings; this.threadPool = builder.threadPool; - this.threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( - threadPool, - settings - ); this.pluginsService = builder.pluginsService; this.nodeEnv = builder.nodeEnv; this.parserConfig = XContentParserConfiguration.EMPTY.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE) @@ -321,6 +317,11 @@ protected void doStart() { this.bigArrays = builder.bigArrays; this.scriptService = builder.scriptService; this.clusterService = builder.clusterService; + this.threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( + threadPool, + clusterService.getClusterSettings(), + nodeEnv + ); this.projectResolver = builder.projectResolver; this.client = builder.client; this.idFieldDataEnabled = INDICES_ID_FIELD_DATA_ENABLED_SETTING.get(clusterService.getSettings()); @@ -368,7 +369,8 @@ public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, lon indicesFieldDataCache, cacheCleaner, indicesRequestCache, - indicesQueryCache + indicesQueryCache, + threadPoolMergeExecutorService ); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java index 043b982ad4344..f53d01d4772a1 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -194,13 +194,17 @@ public void setUp() throws Exception { emptyMap() ); threadPool = new TestThreadPool("test"); - threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService(threadPool, settings); circuitBreakerService = new NoneCircuitBreakerService(); PageCacheRecycler pageCacheRecycler = new PageCacheRecycler(settings); bigArrays = new BigArrays(pageCacheRecycler, circuitBreakerService, CircuitBreaker.REQUEST); scriptService = new ScriptService(settings, Collections.emptyMap(), Collections.emptyMap(), () -> 1L); - clusterService = ClusterServiceUtils.createClusterService(threadPool); + clusterService = ClusterServiceUtils.createClusterService(threadPool, ClusterSettings.createBuiltInClusterSettings(settings)); nodeEnvironment = new NodeEnvironment(settings, environment); + threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( + threadPool, + clusterService.getClusterSettings(), + nodeEnvironment + ); mapperRegistry = new IndicesModule(Collections.emptyList()).getMapperRegistry(); indexNameExpressionResolver = TestIndexNameExpressionResolver.newInstance(threadPool.getThreadContext()); } diff --git a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceDiskSpaceTests.java b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceDiskSpaceTests.java new file mode 100644 index 0000000000000..97943101758fe --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceDiskSpaceTests.java @@ -0,0 +1,1023 @@ +/* + * 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.index.engine; + +import org.apache.lucene.tests.mockfile.FilterFileSystemProvider; +import org.elasticsearch.cluster.routing.allocation.DiskThresholdSettings; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.core.PathUtils; +import org.elasticsearch.core.PathUtilsForTesting; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.core.Tuple; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.NodeEnvironment; +import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; +import java.nio.file.spi.FileSystemProvider; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.ABORT; +import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.BACKLOG; +import static org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule.RUN; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ThreadPoolMergeExecutorServiceDiskSpaceTests extends ESTestCase { + + private static TestMockFileStore aFileStore = new TestMockFileStore("mocka"); + private static TestMockFileStore bFileStore = new TestMockFileStore("mockb"); + private static String aPathPart; + private static String bPathPart; + private static int mergeExecutorThreadCount; + private static Settings settings; + private static TestCapturingThreadPool testThreadPool; + private static NodeEnvironment nodeEnvironment; + + @BeforeClass + public static void installMockUsableSpaceFS() throws Exception { + FileSystem current = PathUtils.getDefaultFileSystem(); + aPathPart = "a-" + randomUUID(); + bPathPart = "b-" + randomUUID(); + FileSystemProvider mock = new TestMockUsableSpaceFileSystemProvider(current); + PathUtilsForTesting.installMock(mock.getFileSystem(null)); + Path path = PathUtils.get(createTempDir().toString()); + // use 2 data paths + String[] paths = new String[] { path.resolve(aPathPart).toString(), path.resolve(bPathPart).toString() }; + // some tests hold one merge thread blocked, and need at least one other runnable + mergeExecutorThreadCount = randomIntBetween(2, 9); + Settings.Builder settingsBuilder = Settings.builder() + .put(Environment.PATH_HOME_SETTING.getKey(), path) + .putList(Environment.PATH_DATA_SETTING.getKey(), paths) + // the default of "5s" slows down testing + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "50ms") + .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount); + if (randomBoolean()) { + settingsBuilder.put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true); + } + settings = settingsBuilder.build(); + testThreadPool = new TestCapturingThreadPool("test", settings); + nodeEnvironment = new NodeEnvironment(settings, TestEnvironment.newEnvironment(settings)); + } + + @AfterClass + public static void removeMockUsableSpaceFS() { + PathUtilsForTesting.teardown(); + aFileStore = null; + bFileStore = null; + testThreadPool.close(); + nodeEnvironment.close(); + } + + @After + public void cleanupThreadPool() { + testThreadPool.scheduledTasks.clear(); + } + + static class TestCapturingThreadPool extends TestThreadPool { + final List> scheduledTasks = new ArrayList<>(); + + TestCapturingThreadPool(String name, Settings settings) { + super(name, settings); + } + + @Override + public Cancellable scheduleWithFixedDelay(Runnable command, TimeValue interval, Executor executor) { + Cancellable cancellable = super.scheduleWithFixedDelay(command, interval, executor); + scheduledTasks.add(new Tuple<>(interval, cancellable)); + return cancellable; + } + } + + static class TestMockUsableSpaceFileSystemProvider extends FilterFileSystemProvider { + + TestMockUsableSpaceFileSystemProvider(FileSystem inner) { + super("mockusablespace://", inner); + } + + @Override + public FileStore getFileStore(Path path) { + if (path.toString().contains(path.getFileSystem().getSeparator() + aPathPart)) { + return aFileStore; + } else { + assert path.toString().contains(path.getFileSystem().getSeparator() + bPathPart); + return bFileStore; + } + } + } + + static class TestMockFileStore extends FileStore { + + public volatile long totalSpace; + public volatile long freeSpace; + public volatile long usableSpace; + public volatile boolean throwIoException; + + private final String desc; + + TestMockFileStore(String desc) { + this.desc = desc; + } + + @Override + public String type() { + return "mock"; + } + + @Override + public String name() { + return desc; + } + + @Override + public String toString() { + return desc; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public long getTotalSpace() throws IOException { + if (throwIoException) { + throw new IOException("Test IO Exception"); + } + return totalSpace; + } + + @Override + public long getUnallocatedSpace() throws IOException { + if (throwIoException) { + throw new IOException("Test IO Exception"); + } + return freeSpace; + } + + @Override + public long getUsableSpace() throws IOException { + if (throwIoException) { + throw new IOException("Test IO Exception"); + } + return usableSpace; + } + + @Override + public boolean supportsFileAttributeView(Class type) { + return false; + } + + @Override + public boolean supportsFileAttributeView(String name) { + return false; + } + + @Override + public V getFileStoreAttributeView(Class type) { + return null; + } + + @Override + public Object getAttribute(String attribute) { + return null; + } + } + + public void testAvailableDiskSpaceMonitorWithDefaultSettings() throws Exception { + // path "a" has lots of free space, and "b" has little + aFileStore.usableSpace = 100_000L; + aFileStore.totalSpace = aFileStore.usableSpace * 2; + bFileStore.usableSpace = 1_000L; + bFileStore.totalSpace = bFileStore.usableSpace * 2; + LinkedHashSet availableDiskSpaceUpdates = new LinkedHashSet<>(); + try ( + var diskSpacePeriodicMonitor = ThreadPoolMergeExecutorService.startDiskSpaceMonitoring( + testThreadPool, + nodeEnvironment.dataPaths(), + ClusterSettings.createBuiltInClusterSettings(settings), + (availableDiskSpace) -> { + synchronized (availableDiskSpaceUpdates) { + availableDiskSpaceUpdates.add(availableDiskSpace); + } + } + ) + ) { + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(1)); + // 100_000 (available) - 5% (default flood stage level) * 200_000 (total space) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(90_000L)); + } + }); + // "b" now has more available space + bFileStore.usableSpace = 110_000L; + bFileStore.totalSpace = 130_000L; + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(2)); + // 110_000 (available) - 5% (default flood stage level) * 130_000 (total space) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(103_500L)); + } + }); + // available space for "a" and "b" is below the limit => it's clamp down to "0" + aFileStore.usableSpace = 100L; + bFileStore.usableSpace = 1_000L; + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(3)); + // 1_000 (available) - 5% (default flood stage level) * 130_000 (total space) < 0 + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(0L)); + } + }); + } + } + + public void testDiskSpaceMonitorStartsAsDisabled() throws Exception { + aFileStore.usableSpace = randomLongBetween(1L, 100L); + aFileStore.totalSpace = randomLongBetween(1L, 100L); + aFileStore.throwIoException = randomBoolean(); + bFileStore.usableSpace = randomLongBetween(1L, 100L); + bFileStore.totalSpace = randomLongBetween(1L, 100L); + bFileStore.throwIoException = randomBoolean(); + Settings.Builder settingsBuilder = Settings.builder().put(settings); + if (randomBoolean()) { + settingsBuilder.put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0"); + } else { + settingsBuilder.put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s"); + } + Settings settings = settingsBuilder.build(); + ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(settings); + LinkedHashSet availableDiskSpaceUpdates = new LinkedHashSet<>(); + try ( + var diskSpacePeriodicMonitor = ThreadPoolMergeExecutorService.startDiskSpaceMonitoring( + testThreadPool, + nodeEnvironment.dataPaths(), + clusterSettings, + (availableDiskSpace) -> { + synchronized (availableDiskSpaceUpdates) { + availableDiskSpaceUpdates.add(availableDiskSpace); + } + } + ) + ) { + assertThat(diskSpacePeriodicMonitor.isScheduled(), is(false)); + assertThat(availableDiskSpaceUpdates.size(), is(1)); + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(Long.MAX_VALUE)); + // updating monitoring interval should enable the monitor + String intervalSettingValue = randomFrom("1s", "123ms", "5nanos", "2h"); + clusterSettings.applySettings( + Settings.builder() + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), intervalSettingValue) + .build() + ); + assertThat(diskSpacePeriodicMonitor.isScheduled(), is(true)); + assertThat(testThreadPool.scheduledTasks.size(), is(1)); + assertThat( + testThreadPool.scheduledTasks.getLast().v1(), + is( + TimeValue.parseTimeValue( + intervalSettingValue, + ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey() + ) + ) + ); + } + aFileStore.throwIoException = false; + bFileStore.throwIoException = false; + } + + public void testAvailableDiskSpaceMonitorWhenFileSystemStatErrors() throws Exception { + aFileStore.usableSpace = randomLongBetween(1L, 100L); + aFileStore.totalSpace = randomLongBetween(1L, 100L); + bFileStore.usableSpace = randomLongBetween(1L, 100L); + bFileStore.totalSpace = randomLongBetween(1L, 100L); + boolean aErrorsFirst = randomBoolean(); + if (aErrorsFirst) { + // the "a" file system will error when collecting stats + aFileStore.throwIoException = true; + bFileStore.throwIoException = false; + } else { + aFileStore.throwIoException = false; + bFileStore.throwIoException = true; + } + LinkedHashSet availableDiskSpaceUpdates = new LinkedHashSet<>(); + try ( + var diskSpacePeriodicMonitor = ThreadPoolMergeExecutorService.startDiskSpaceMonitoring( + testThreadPool, + nodeEnvironment.dataPaths(), + ClusterSettings.createBuiltInClusterSettings(settings), + (availableDiskSpace) -> { + synchronized (availableDiskSpaceUpdates) { + availableDiskSpaceUpdates.add(availableDiskSpace); + } + } + ) + ) { + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(1)); + if (aErrorsFirst) { + // uses the stats from "b" + assertThat( + availableDiskSpaceUpdates.getLast().getBytes(), + // the default 5% (same as flood stage level) + is(Math.max(bFileStore.usableSpace - bFileStore.totalSpace / 20, 0L)) + ); + } else { + // uses the stats from "a" + assertThat( + availableDiskSpaceUpdates.getLast().getBytes(), + // the default 5% (same as flood stage level) + is(Math.max(aFileStore.usableSpace - aFileStore.totalSpace / 20, 0L)) + ); + } + } + }); + if (aErrorsFirst) { + // the "b" file system will also now error when collecting stats + bFileStore.throwIoException = true; + } else { + // the "a" file system will also now error when collecting stats + aFileStore.throwIoException = true; + } + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(2)); + // consider the available disk space as unlimited when no fs stats can be collected + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(Long.MAX_VALUE)); + } + }); + if (aErrorsFirst) { + // "a" fs stats collection recovered + aFileStore.throwIoException = false; + } else { + // "b" fs stats collection recovered + bFileStore.throwIoException = false; + } + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(3)); + if (aErrorsFirst) { + // uses the stats from "a" + assertThat( + availableDiskSpaceUpdates.getLast().getBytes(), + // the default 5% (same as flood stage level) + is(Math.max(aFileStore.usableSpace - aFileStore.totalSpace / 20, 0L)) + ); + } else { + // uses the stats from "b" + assertThat( + availableDiskSpaceUpdates.getLast().getBytes(), + // the default 5% (same as flood stage level) + is(Math.max(bFileStore.usableSpace - bFileStore.totalSpace / 20, 0L)) + ); + } + } + }); + } + aFileStore.throwIoException = false; + bFileStore.throwIoException = false; + } + + public void testAvailableDiskSpaceMonitorSettingsUpdate() throws Exception { + ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(settings); + // path "b" has more usable (available) space, but path "a" has more total space + aFileStore.usableSpace = 900_000L; + aFileStore.totalSpace = 1_200_000L; + bFileStore.usableSpace = 1_000_000L; + bFileStore.totalSpace = 1_100_000L; + LinkedHashSet availableDiskSpaceUpdates = new LinkedHashSet<>(); + try ( + var diskSpacePeriodicMonitor = ThreadPoolMergeExecutorService.startDiskSpaceMonitoring( + testThreadPool, + nodeEnvironment.dataPaths(), + clusterSettings, + (availableDiskSpace) -> { + synchronized (availableDiskSpaceUpdates) { + availableDiskSpaceUpdates.add(availableDiskSpace); + } + } + ) + ) { + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(1)); + // 1_000_000 (available) - 5% (default flood stage level) * 1_100_000 (total space) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(945_000L)); + } + }, 5, TimeUnit.SECONDS); + // updated the ration for the watermark + clusterSettings.applySettings( + Settings.builder().put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING.getKey(), "90%").build() + ); + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(2)); + // 1_000_000 (available) - 10% (indices.merge.disk.watermark.high) * 1_100_000 (total space) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(890_000L)); + } + }, 5, TimeUnit.SECONDS); + // absolute value for the watermark limit + clusterSettings.applySettings( + Settings.builder().put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING.getKey(), "3000b").build() + ); + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(3)); + // 1_000_000 (available) - 3_000 (indices.merge.disk.watermark.high) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(997_000L)); + } + }, 5, TimeUnit.SECONDS); + // headroom value that takes priority over the watermark + clusterSettings.applySettings( + Settings.builder() + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING.getKey(), "50%") + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING.getKey(), "11111b") + .build() + ); + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(4)); + // 1_000_000 (available) - 11_111 (indices.merge.disk.watermark.high) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(988_889L)); + } + }, 5, TimeUnit.SECONDS); + // watermark limit that takes priority over the headroom + clusterSettings.applySettings( + Settings.builder() + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING.getKey(), "98%") + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING.getKey(), "22222b") + .build() + ); + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(5)); + // 1_000_000 (available) - 2% (indices.merge.disk.watermark.high) * 1_100_000 (total space) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(978_000L)); + } + }, 5, TimeUnit.SECONDS); + // headroom takes priority over the default watermark of 95% + clusterSettings.applySettings( + Settings.builder() + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING.getKey(), "22222b") + .build() + ); + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(6)); + // 1_000_000 (available) - 22_222 + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(977_778L)); + } + }, 5, TimeUnit.SECONDS); + // watermark from routing allocation takes priority + clusterSettings.applySettings( + Settings.builder() + .put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_WATERMARK_SETTING.getKey(), "99%") + .put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_MAX_HEADROOM_SETTING.getKey(), "2b") + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_MAX_HEADROOM_SETTING.getKey(), "22222b") + .build() + ); + assertBusy(() -> { + synchronized (availableDiskSpaceUpdates) { + assertThat(availableDiskSpaceUpdates.size(), is(7)); + // 1_000_000 (available) - 1% (cluster.routing.allocation.disk.watermark.flood_stage) * 1_100_000 (total space) + assertThat(availableDiskSpaceUpdates.getLast().getBytes(), is(989_000L)); + } + }, 5, TimeUnit.SECONDS); + } + } + + public void testAbortingOrRunningMergeTaskHoldsUpBudget() throws Exception { + aFileStore.totalSpace = randomLongBetween(1_000L, 10_000L); + bFileStore.totalSpace = randomLongBetween(1_000L, 10_000L); + aFileStore.usableSpace = randomLongBetween(900L, aFileStore.totalSpace); + bFileStore.usableSpace = randomLongBetween(900L, bFileStore.totalSpace); + boolean aHasMoreSpace = aFileStore.usableSpace > bFileStore.usableSpace; + try ( + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService + .maybeCreateThreadPoolMergeExecutorService( + testThreadPool, + ClusterSettings.createBuiltInClusterSettings(settings), + nodeEnvironment + ) + ) { + assert threadPoolMergeExecutorService != null; + assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), greaterThanOrEqualTo(1)); + // assumes the 5% default value for the remaining space watermark + final long availableInitialBudget = aHasMoreSpace + ? aFileStore.usableSpace - aFileStore.totalSpace / 20 + : bFileStore.usableSpace - bFileStore.totalSpace / 20; + final AtomicLong expectedAvailableBudget = new AtomicLong(availableInitialBudget); + // wait for the merge scheduler to learn about the available disk space + assertBusy( + () -> assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())) + ); + ThreadPoolMergeScheduler.MergeTask stallingMergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + long taskBudget = randomLongBetween(1L, expectedAvailableBudget.get()); + when(stallingMergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + when(stallingMergeTask.schedule()).thenReturn(randomFrom(RUN, ABORT)); + CountDownLatch testDoneLatch = new CountDownLatch(1); + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + testDoneLatch.await(); + return null; + }).when(stallingMergeTask).run(); + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + testDoneLatch.await(); + return null; + }).when(stallingMergeTask).abort(); + threadPoolMergeExecutorService.submitMergeTask(stallingMergeTask); + // assert the merge task is holding up disk space budget + expectedAvailableBudget.set(expectedAvailableBudget.get() - taskBudget); + assertBusy( + () -> assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())) + ); + // double check that submitting a runnable merge task under budget works correctly + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(randomLongBetween(0L, expectedAvailableBudget.get())); + when(mergeTask.schedule()).thenReturn(RUN); + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + assertBusy(() -> { + verify(mergeTask).schedule(); + verify(mergeTask).run(); + verify(mergeTask, times(0)).abort(); + }); + // let the test finish + testDoneLatch.countDown(); + assertBusy(() -> { + // available budget is back to the initial value + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(availableInitialBudget)); + if (stallingMergeTask.schedule() == RUN) { + verify(stallingMergeTask).run(); + verify(stallingMergeTask, times(0)).abort(); + } else { + verify(stallingMergeTask).abort(); + verify(stallingMergeTask, times(0)).run(); + } + assertThat(threadPoolMergeExecutorService.allDone(), is(true)); + }); + } + } + + public void testBackloggedMergeTasksDoNotHoldUpBudget() throws Exception { + aFileStore.totalSpace = randomLongBetween(1_000L, 10_000L); + bFileStore.totalSpace = randomLongBetween(1_000L, 10_000L); + aFileStore.usableSpace = randomLongBetween(900L, aFileStore.totalSpace); + bFileStore.usableSpace = randomLongBetween(900L, bFileStore.totalSpace); + boolean aHasMoreSpace = aFileStore.usableSpace > bFileStore.usableSpace; + try ( + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService + .maybeCreateThreadPoolMergeExecutorService( + testThreadPool, + ClusterSettings.createBuiltInClusterSettings(settings), + nodeEnvironment + ) + ) { + assert threadPoolMergeExecutorService != null; + assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), greaterThanOrEqualTo(1)); + // assumes the 5% default value for the remaining space watermark + final long availableInitialBudget = aHasMoreSpace + ? aFileStore.usableSpace - aFileStore.totalSpace / 20 + : bFileStore.usableSpace - bFileStore.totalSpace / 20; + final AtomicLong expectedAvailableBudget = new AtomicLong(availableInitialBudget); + assertBusy( + () -> assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())) + ); + long backloggedMergeTaskDiskSpaceBudget = randomLongBetween(1L, expectedAvailableBudget.get()); + CountDownLatch testDoneLatch = new CountDownLatch(1); + // take care that there's still at least one thread available to run merges + int maxBlockingTasksToSubmit = mergeExecutorThreadCount - 1; + // first maybe submit some running or aborting merge tasks that hold up some budget while running or aborting + List runningMergeTasks = new ArrayList<>(); + List abortingMergeTasks = new ArrayList<>(); + while (expectedAvailableBudget.get() - backloggedMergeTaskDiskSpaceBudget > 0L + && maxBlockingTasksToSubmit-- > 0 + && randomBoolean()) { + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + long taskBudget = randomLongBetween(1L, expectedAvailableBudget.get() - backloggedMergeTaskDiskSpaceBudget); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + when(mergeTask.schedule()).thenReturn(randomFrom(RUN, ABORT)); + // this task runs/aborts, and it's going to hold up some budget for it + expectedAvailableBudget.set(expectedAvailableBudget.get() - taskBudget); + // this task will hold up budget because it blocks when it runs (to simulate it running for a long time) + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + testDoneLatch.await(); + return null; + }).when(mergeTask).run(); + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + testDoneLatch.await(); + return null; + }).when(mergeTask).abort(); + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + if (mergeTask.schedule() == RUN) { + runningMergeTasks.add(mergeTask); + } else { + abortingMergeTasks.add(mergeTask); + } + } + assertBusy( + () -> assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())) + ); + // submit some backlogging merge tasks which should NOT hold up any budget + IdentityHashMap backloggingMergeTasksScheduleCountMap = new IdentityHashMap<>(); + int backloggingTaskCount = randomIntBetween(1, 10); + while (backloggingTaskCount-- > 0) { + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + long taskBudget = randomLongBetween(1L, backloggedMergeTaskDiskSpaceBudget); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + doAnswer(mock -> { + // task always backlogs (as long as the test hasn't finished) + if (testDoneLatch.getCount() > 0) { + return BACKLOG; + } else { + return RUN; + } + }).when(mergeTask).schedule(); + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + backloggingMergeTasksScheduleCountMap.put(mergeTask, 1); + } + int checkRounds = randomIntBetween(1, 10); + // assert all backlogging merge tasks have been scheduled while possibly re-enqueued, + // BUT none run and none aborted, AND the available budget is left unchanged + while (true) { + assertBusy(() -> { + for (ThreadPoolMergeScheduler.MergeTask mergeTask : backloggingMergeTasksScheduleCountMap.keySet()) { + verify(mergeTask, times(backloggingMergeTasksScheduleCountMap.get(mergeTask))).schedule(); + } + for (ThreadPoolMergeScheduler.MergeTask mergeTask : backloggingMergeTasksScheduleCountMap.keySet()) { + verify(mergeTask, times(0)).run(); + verify(mergeTask, times(0)).abort(); + } + // budget hasn't changed! + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())); + }); + if (checkRounds-- <= 0) { + break; + } + // maybe re-enqueue backlogged merge task + for (ThreadPoolMergeScheduler.MergeTask backlogged : backloggingMergeTasksScheduleCountMap.keySet()) { + if (randomBoolean()) { + threadPoolMergeExecutorService.reEnqueueBackloggedMergeTask(backlogged); + backloggingMergeTasksScheduleCountMap.put(backlogged, backloggingMergeTasksScheduleCountMap.get(backlogged) + 1); + } + } + // double check that submitting a runnable merge task under budget works correctly + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + long taskBudget = randomLongBetween(1L, backloggedMergeTaskDiskSpaceBudget); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + when(mergeTask.schedule()).thenReturn(RUN); + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + assertBusy(() -> { + verify(mergeTask).schedule(); + verify(mergeTask).run(); + verify(mergeTask, times(0)).abort(); + }); + } + // let the test finish + testDoneLatch.countDown(); + for (ThreadPoolMergeScheduler.MergeTask backlogged : backloggingMergeTasksScheduleCountMap.keySet()) { + threadPoolMergeExecutorService.reEnqueueBackloggedMergeTask(backlogged); + } + assertBusy(() -> { + for (ThreadPoolMergeScheduler.MergeTask mergeTask : runningMergeTasks) { + verify(mergeTask).run(); + } + for (ThreadPoolMergeScheduler.MergeTask mergeTask : abortingMergeTasks) { + verify(mergeTask).abort(); + } + for (ThreadPoolMergeScheduler.MergeTask backlogged : backloggingMergeTasksScheduleCountMap.keySet()) { + verify(backlogged).run(); + } + // available budget is restored + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(availableInitialBudget)); + assertThat(threadPoolMergeExecutorService.allDone(), is(true)); + }); + } + } + + public void testUnavailableBudgetBlocksNewMergeTasksFromStartingExecution() throws Exception { + aFileStore.totalSpace = 150_000L; + bFileStore.totalSpace = 140_000L; + boolean aHasMoreSpace = randomBoolean(); + if (aHasMoreSpace) { + // "a" has more available space + aFileStore.usableSpace = 120_000L; + bFileStore.usableSpace = 100_000L; + } else { + // "b" has more available space + aFileStore.usableSpace = 90_000L; + bFileStore.usableSpace = 110_000L; + } + try ( + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService + .maybeCreateThreadPoolMergeExecutorService( + testThreadPool, + ClusterSettings.createBuiltInClusterSettings(settings), + nodeEnvironment + ) + ) { + assert threadPoolMergeExecutorService != null; + // wait for the budget to be updated from the available disk space + AtomicLong expectedAvailableBudget = new AtomicLong(); + assertBusy(() -> { + if (aHasMoreSpace) { + // 120_000L (available) - 5% (default flood stage level) * 150_000L (total) + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(112_500L)); + expectedAvailableBudget.set(112_500L); + } else { + // 110_000L (available) - 5% (default flood stage level) * 140_000L (total) + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(103_000L)); + expectedAvailableBudget.set(103_000L); + } + }); + List runningOrAbortingMergeTasksList = new ArrayList<>(); + List latchesBlockingMergeTasksList = new ArrayList<>(); + int submittedMergesCount = randomIntBetween(1, mergeExecutorThreadCount - 1); + // submit merge tasks that don't finish, in order to deplete the available budget + while (submittedMergesCount > 0 && expectedAvailableBudget.get() > 0L) { + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + when(mergeTask.supportsIOThrottling()).thenReturn(randomBoolean()); + doAnswer(mock -> { + Schedule schedule = randomFrom(Schedule.values()); + if (schedule == BACKLOG) { + testThreadPool.executor(ThreadPool.Names.GENERIC).execute(() -> { + // re-enqueue backlogged merge task + threadPoolMergeExecutorService.reEnqueueBackloggedMergeTask(mergeTask); + }); + } + return schedule; + }).when(mergeTask).schedule(); + // let some task complete, which will NOT hold up any budget + if (randomBoolean()) { + // this task will NOT hold up any budget because it runs quickly (it is not blocked) + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(randomLongBetween(1_000L, 10_000L)); + } else { + CountDownLatch blockMergeTaskLatch = new CountDownLatch(1); + long taskBudget = randomLongBetween(1L, expectedAvailableBudget.get()); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + expectedAvailableBudget.set(expectedAvailableBudget.get() - taskBudget); + submittedMergesCount--; + // this task will hold up budget because it blocks when it runs (to simulate it running for a long time) + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + blockMergeTaskLatch.await(); + return null; + }).when(mergeTask).run(); + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + blockMergeTaskLatch.await(); + return null; + }).when(mergeTask).abort(); + runningOrAbortingMergeTasksList.add(mergeTask); + latchesBlockingMergeTasksList.add(blockMergeTaskLatch); + } + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + } + // currently running (or aborting) merge tasks have consumed some of the available budget + while (runningOrAbortingMergeTasksList.isEmpty() == false) { + assertBusy( + () -> assertThat( + threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), + is(expectedAvailableBudget.get()) + ) + ); + ThreadPoolMergeScheduler.MergeTask mergeTask1 = mock(ThreadPoolMergeScheduler.MergeTask.class); + when(mergeTask1.supportsIOThrottling()).thenReturn(randomBoolean()); + when(mergeTask1.schedule()).thenReturn(RUN); + ThreadPoolMergeScheduler.MergeTask mergeTask2 = mock(ThreadPoolMergeScheduler.MergeTask.class); + when(mergeTask2.supportsIOThrottling()).thenReturn(randomBoolean()); + when(mergeTask2.schedule()).thenReturn(RUN); + boolean task1Runs = randomBoolean(); + long currentAvailableBudget = expectedAvailableBudget.get(); + long overBudget = randomLongBetween(currentAvailableBudget + 1L, currentAvailableBudget + 100L); + long underBudget = randomLongBetween(0L, currentAvailableBudget); + if (task1Runs) { + // merge task 1 can run because it is under budget + when(mergeTask1.estimatedRemainingMergeSize()).thenReturn(underBudget); + // merge task 2 cannot run because it is over budget + when(mergeTask2.estimatedRemainingMergeSize()).thenReturn(overBudget); + } else { + // merge task 1 cannot run because it is over budget + when(mergeTask1.estimatedRemainingMergeSize()).thenReturn(overBudget); + // merge task 2 can run because it is under budget + when(mergeTask2.estimatedRemainingMergeSize()).thenReturn(underBudget); + } + threadPoolMergeExecutorService.submitMergeTask(mergeTask1); + threadPoolMergeExecutorService.submitMergeTask(mergeTask2); + assertBusy(() -> { + if (task1Runs) { + verify(mergeTask1).schedule(); + verify(mergeTask1).run(); + verify(mergeTask2, times(0)).schedule(); + verify(mergeTask2, times(0)).run(); + } else { + verify(mergeTask2).schedule(); + verify(mergeTask2).run(); + verify(mergeTask1, times(0)).schedule(); + verify(mergeTask1, times(0)).run(); + } + }); + // let one task finish from the bunch that is holding up budget + int index = randomIntBetween(0, runningOrAbortingMergeTasksList.size() - 1); + latchesBlockingMergeTasksList.remove(index).countDown(); + ThreadPoolMergeScheduler.MergeTask completedMergeTask = runningOrAbortingMergeTasksList.remove(index); + // update the expected budget given that one task now finished + expectedAvailableBudget.set(expectedAvailableBudget.get() + completedMergeTask.estimatedRemainingMergeSize()); + } + // let the test finish cleanly + assertBusy(() -> { + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(aHasMoreSpace ? 112_500L : 103_000L)); + assertThat(threadPoolMergeExecutorService.allDone(), is(true)); + }); + } + } + + public void testMergeTasksAreUnblockedWhenMoreDiskSpaceBecomesAvailable() throws Exception { + aFileStore.totalSpace = randomLongBetween(300L, 1_000L); + bFileStore.totalSpace = randomLongBetween(300L, 1_000L); + long grantedUsableSpaceBuffer = randomLongBetween(10L, 50L); + aFileStore.usableSpace = randomLongBetween(200L, aFileStore.totalSpace - grantedUsableSpaceBuffer); + bFileStore.usableSpace = randomLongBetween(200L, bFileStore.totalSpace - grantedUsableSpaceBuffer); + boolean aHasMoreSpace = aFileStore.usableSpace > bFileStore.usableSpace; + Settings.Builder settingsBuilder = Settings.builder().put(settings); + // change the watermark level, just for coverage and it's easier with the calculations + if (randomBoolean()) { + settingsBuilder.put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_HIGH_WATERMARK_SETTING.getKey(), "90%"); + } else { + settingsBuilder.put(DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_FLOOD_STAGE_WATERMARK_SETTING.getKey(), "90%"); + } + try ( + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService + .maybeCreateThreadPoolMergeExecutorService( + testThreadPool, + ClusterSettings.createBuiltInClusterSettings(settingsBuilder.build()), + nodeEnvironment + ) + ) { + assert threadPoolMergeExecutorService != null; + assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), greaterThanOrEqualTo(1)); + // uses the 10% watermark limit + final long availableInitialBudget = aHasMoreSpace + ? aFileStore.usableSpace - aFileStore.totalSpace / 10 + : bFileStore.usableSpace - bFileStore.totalSpace / 10; + final AtomicLong expectedAvailableBudget = new AtomicLong(availableInitialBudget); + assertBusy( + () -> assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())) + ); + // maybe let some merge tasks hold up some budget + // take care that there's still at least one thread available to run merges + int maxBlockingTasksToSubmit = mergeExecutorThreadCount - 1; + // first maybe submit some running or aborting merge tasks that hold up some budget while running or aborting + List runningMergeTasks = new ArrayList<>(); + List abortingMergeTasks = new ArrayList<>(); + CountDownLatch testDoneLatch = new CountDownLatch(1); + while (expectedAvailableBudget.get() > 0L && maxBlockingTasksToSubmit-- > 0 && randomBoolean()) { + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + long taskBudget = randomLongBetween(1L, expectedAvailableBudget.get()); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + when(mergeTask.schedule()).thenReturn(randomFrom(RUN, ABORT)); + // this task runs/aborts, and it's going to hold up some budget for it + expectedAvailableBudget.set(expectedAvailableBudget.get() - taskBudget); + // this task will hold up budget because it blocks when it runs (to simulate it running for a long time) + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + testDoneLatch.await(); + return null; + }).when(mergeTask).run(); + doAnswer(mock -> { + // wait to be signalled before completing (this holds up budget) + testDoneLatch.await(); + return null; + }).when(mergeTask).abort(); + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + if (mergeTask.schedule() == RUN) { + runningMergeTasks.add(mergeTask); + } else { + abortingMergeTasks.add(mergeTask); + } + } + assertBusy(() -> { + assertThat(threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(0)); + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())); + }); + // send some runnable merge tasks that although runnable are currently over budget + int overBudgetTaskCount = randomIntBetween(1, 5); + List overBudgetTasksToRunList = new ArrayList<>(); + List overBudgetTasksToAbortList = new ArrayList<>(); + while (overBudgetTaskCount-- > 0) { + ThreadPoolMergeScheduler.MergeTask mergeTask = mock(ThreadPoolMergeScheduler.MergeTask.class); + // currently over-budget + long taskBudget = randomLongBetween( + expectedAvailableBudget.get() + 1L, + expectedAvailableBudget.get() + grantedUsableSpaceBuffer + ); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(taskBudget); + Schedule schedule = randomFrom(RUN, ABORT); + when(mergeTask.schedule()).thenReturn(schedule); + threadPoolMergeExecutorService.submitMergeTask(mergeTask); + if (schedule == RUN) { + overBudgetTasksToRunList.add(mergeTask); + } else { + overBudgetTasksToAbortList.add(mergeTask); + } + } + // over-budget tasks did not run, are enqueued, and budget is unchanged + assertBusy(() -> { + for (ThreadPoolMergeScheduler.MergeTask mergeTask : overBudgetTasksToAbortList) { + verify(mergeTask, times(0)).schedule(); + verify(mergeTask, times(0)).run(); + verify(mergeTask, times(0)).abort(); + } + for (ThreadPoolMergeScheduler.MergeTask mergeTask : overBudgetTasksToRunList) { + verify(mergeTask, times(0)).schedule(); + verify(mergeTask, times(0)).run(); + verify(mergeTask, times(0)).abort(); + } + assertThat( + threadPoolMergeExecutorService.getMergeTasksQueueLength(), + is(overBudgetTasksToAbortList.size() + overBudgetTasksToRunList.size()) + ); + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())); + }); + // more disk space becomes available + if (aHasMoreSpace) { + aFileStore.usableSpace += grantedUsableSpaceBuffer; + } else { + bFileStore.usableSpace += grantedUsableSpaceBuffer; + } + expectedAvailableBudget.set(expectedAvailableBudget.get() + grantedUsableSpaceBuffer); + // all over-budget tasks can now run because more disk space became available + assertBusy(() -> { + for (ThreadPoolMergeScheduler.MergeTask mergeTask : overBudgetTasksToRunList) { + verify(mergeTask).schedule(); + verify(mergeTask).run(); + verify(mergeTask, times(0)).abort(); + } + for (ThreadPoolMergeScheduler.MergeTask mergeTask : overBudgetTasksToAbortList) { + verify(mergeTask).schedule(); + verify(mergeTask, times(0)).run(); + verify(mergeTask).abort(); + } + assertThat(threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(0)); + assertThat(threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), is(expectedAvailableBudget.get())); + }); + // let test finish cleanly + testDoneLatch.countDown(); + assertBusy(() -> { + for (ThreadPoolMergeScheduler.MergeTask mergeTask : runningMergeTasks) { + verify(mergeTask).run(); + } + for (ThreadPoolMergeScheduler.MergeTask mergeTask : abortingMergeTasks) { + verify(mergeTask).abort(); + } + assertThat( + threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), + is(availableInitialBudget + grantedUsableSpaceBuffer) + ); + assertThat(threadPoolMergeExecutorService.allDone(), is(true)); + assertThat( + threadPoolMergeExecutorService.getDiskSpaceAvailableForNewMergeTasks(), + is(availableInitialBudget + grantedUsableSpaceBuffer) + ); + }); + } + } +} diff --git a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceTests.java b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceTests.java index bcc5250ea098d..9b74d68326108 100644 --- a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceTests.java +++ b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeExecutorServiceTests.java @@ -9,21 +9,28 @@ package org.elasticsearch.index.engine; +import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.common.util.concurrent.DeterministicTaskQueue; import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.env.NodeEnvironment; +import org.elasticsearch.index.engine.ThreadPoolMergeExecutorService.MergeTaskPriorityBlockingQueue; +import org.elasticsearch.index.engine.ThreadPoolMergeExecutorService.PriorityBlockingQueueWithBudget; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler.MergeTask; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler.Schedule; import org.elasticsearch.index.merge.OnGoingMerge; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; +import org.junit.After; import org.mockito.ArgumentCaptor; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.IdentityHashMap; import java.util.List; import java.util.PriorityQueue; import java.util.Set; @@ -43,6 +50,7 @@ import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; @@ -56,9 +64,24 @@ public class ThreadPoolMergeExecutorServiceTests extends ESTestCase { - public void testNewMergeTaskIsAbortedWhenThreadPoolIsShutdown() { - TestThreadPool testThreadPool = new TestThreadPool("test"); - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + private NodeEnvironment nodeEnvironment; + + @After + public void closeNodeEnv() { + if (nodeEnvironment != null) { + nodeEnvironment.close(); + nodeEnvironment = null; + } + } + + public void testNewMergeTaskIsAbortedWhenThreadPoolIsShutdown() throws IOException { + TestThreadPool testThreadPool = new TestThreadPool("test", Settings.EMPTY); + nodeEnvironment = newNodeEnvironment(Settings.EMPTY); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + Settings.EMPTY, + nodeEnvironment + ); // shutdown the thread pool testThreadPool.shutdown(); MergeTask mergeTask = mock(MergeTask.class); @@ -78,9 +101,16 @@ public void testEnqueuedAndBackloggedMergesAreStillExecutedWhenThreadPoolIsShutd Settings settings = Settings.builder() .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true) .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); TestThreadPool testThreadPool = new TestThreadPool("test", settings); - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + nodeEnvironment = newNodeEnvironment(settings); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + settings, + nodeEnvironment + ); var countingListener = new CountingMergeEventListener(); threadPoolMergeExecutorService.registerMergeEventListener(countingListener); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); @@ -189,9 +219,16 @@ public void testTargetIORateChangesWhenSubmittingMergeTasks() throws Exception { Settings settings = Settings.builder() .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true) .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + settings, + nodeEnvironment + ); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); Semaphore runMergeSemaphore = new Semaphore(0); AtomicInteger submittedIOThrottledMergeTasks = new AtomicInteger(); @@ -269,9 +306,16 @@ public void testIORateIsAdjustedForRunningMergeTasks() throws Exception { Settings settings = Settings.builder() .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true) .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + settings, + nodeEnvironment + ); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) testThreadPool.executor(ThreadPool.Names.MERGE); Semaphore runMergeSemaphore = new Semaphore(0); @@ -333,7 +377,7 @@ public void testIORateIsAdjustedForRunningMergeTasks() throws Exception { } } - public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsSpeedy() { + public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsSpeedy() throws IOException { // the executor runs merge tasks at a faster rate than the rate that merge tasks are submitted int submittedVsExecutedRateOutOf1000 = randomIntBetween(0, 250); testIORateAdjustedForSubmittedTasks(randomIntBetween(50, 1000), submittedVsExecutedRateOutOf1000, randomIntBetween(0, 5)); @@ -341,7 +385,7 @@ public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsSpeedy() { testIORateAdjustedForSubmittedTasks(randomIntBetween(50, 1000), submittedVsExecutedRateOutOf1000, randomIntBetween(5, 50)); } - public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsSluggish() { + public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsSluggish() throws IOException { // the executor runs merge tasks at a faster rate than the rate that merge tasks are submitted int submittedVsExecutedRateOutOf1000 = randomIntBetween(750, 1000); testIORateAdjustedForSubmittedTasks(randomIntBetween(50, 1000), submittedVsExecutedRateOutOf1000, randomIntBetween(0, 5)); @@ -349,7 +393,7 @@ public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsSluggish() { testIORateAdjustedForSubmittedTasks(randomIntBetween(50, 1000), submittedVsExecutedRateOutOf1000, randomIntBetween(5, 50)); } - public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsOnPar() { + public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsOnPar() throws IOException { // the executor runs merge tasks at a faster rate than the rate that merge tasks are submitted int submittedVsExecutedRateOutOf1000 = randomIntBetween(250, 750); testIORateAdjustedForSubmittedTasks(randomIntBetween(50, 1000), submittedVsExecutedRateOutOf1000, randomIntBetween(0, 5)); @@ -357,14 +401,24 @@ public void testIORateAdjustedForSubmittedTasksWhenExecutionRateIsOnPar() { testIORateAdjustedForSubmittedTasks(randomIntBetween(50, 1000), submittedVsExecutedRateOutOf1000, randomIntBetween(5, 50)); } - private void testIORateAdjustedForSubmittedTasks( - int totalTasksToSubmit, - int submittedVsExecutedRateOutOf1000, - int initialTasksToSubmit - ) { + private void testIORateAdjustedForSubmittedTasks(int totalTasksToSubmit, int submittedVsExecutedRateOutOf1000, int initialTasksToSubmit) + throws IOException { DeterministicTaskQueue mergeExecutorTaskQueue = new DeterministicTaskQueue(); ThreadPool mergeExecutorThreadPool = mergeExecutorTaskQueue.getThreadPool(); - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(mergeExecutorThreadPool); + Settings settings = Settings.builder() + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") + .build(); + if (nodeEnvironment != null) { + nodeEnvironment.close(); + nodeEnvironment = null; + } + nodeEnvironment = newNodeEnvironment(settings); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + mergeExecutorThreadPool, + settings, + nodeEnvironment + ); final AtomicInteger currentlySubmittedMergeTaskCount = new AtomicInteger(); final AtomicLong targetIORateLimit = new AtomicLong(ThreadPoolMergeExecutorService.START_IO_RATE.getBytes()); final AtomicReference lastRunTask = new AtomicReference<>(); @@ -422,9 +476,16 @@ public void testMergeTasksRunConcurrently() throws Exception { Settings settings = Settings.builder() .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true) .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + settings, + nodeEnvironment + ); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); // more merge tasks than max concurrent merges allowed to run concurrently int totalMergeTasksCount = mergeExecutorThreadCount + randomIntBetween(1, 5); @@ -465,7 +526,7 @@ public void testMergeTasksRunConcurrently() throws Exception { assertThat(threadPoolMergeExecutorService.getRunningMergeTasks().size(), is(mergeExecutorThreadCount)); // with the other merge tasks enqueued assertThat( - threadPoolMergeExecutorService.getQueuedMergeTasks().size(), + threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(totalMergeTasksCount - mergeExecutorThreadCount - finalCompletedTasksCount) ); // also check thread-pool stats for the same @@ -485,7 +546,7 @@ public void testMergeTasksRunConcurrently() throws Exception { // there are fewer available merges than available threads assertThat(threadPoolMergeExecutorService.getRunningMergeTasks().size(), is(finalRemainingMergeTasksCount)); // no more merges enqueued - assertThat(threadPoolMergeExecutorService.getQueuedMergeTasks().size(), is(0)); + assertThat(threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(0)); // also check thread-pool stats for the same assertThat(threadPoolExecutor.getActiveCount(), is(finalRemainingMergeTasksCount)); assertThat(threadPoolExecutor.getQueue().size(), is(0)); @@ -502,9 +563,16 @@ public void testThreadPoolStatsWithBackloggedMergeTasks() throws Exception { Settings settings = Settings.builder() .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true) .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + settings, + nodeEnvironment + ); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); int totalMergeTasksCount = randomIntBetween(1, 10); ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) testThreadPool.executor(ThreadPool.Names.MERGE); @@ -533,7 +601,7 @@ public void testThreadPoolStatsWithBackloggedMergeTasks() throws Exception { assertThat(threadPoolExecutor.getActiveCount(), is(backloggedMergeTasksList.size())); assertThat(threadPoolExecutor.getQueue().size(), is(0)); } - assertThat(threadPoolMergeExecutorService.getQueuedMergeTasks().size(), is(0)); + assertThat(threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(0)); }); // re-enqueue backlogged merge tasks for (MergeTask backloggedMergeTask : backloggedMergeTasksList) { @@ -555,9 +623,16 @@ public void testBackloggedMergeTasksExecuteExactlyOnce() throws Exception { .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true) // few merge threads, in order to increase contention .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(testThreadPool); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + testThreadPool, + settings, + nodeEnvironment + ); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); // many merge tasks concurrently int mergeTaskCount = randomIntBetween(10, 100); @@ -613,22 +688,31 @@ public void testBackloggedMergeTasksExecuteExactlyOnce() throws Exception { } } - public void testMergeTasksExecuteInSizeOrder() { + public void testMergeTasksExecuteInSizeOrder() throws IOException { DeterministicTaskQueue mergeExecutorTaskQueue = new DeterministicTaskQueue(); ThreadPool mergeExecutorThreadPool = mergeExecutorTaskQueue.getThreadPool(); - ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService(mergeExecutorThreadPool); + Settings settings = Settings.builder() + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") + .build(); + nodeEnvironment = newNodeEnvironment(settings); + ThreadPoolMergeExecutorService threadPoolMergeExecutorService = getThreadPoolMergeExecutorService( + mergeExecutorThreadPool, + settings, + nodeEnvironment + ); DeterministicTaskQueue reEnqueueBackloggedTaskQueue = new DeterministicTaskQueue(); int mergeTaskCount = randomIntBetween(10, 100); // sort merge tasks available to run by size PriorityQueue mergeTasksAvailableToRun = new PriorityQueue<>( mergeTaskCount, - Comparator.comparingLong(MergeTask::estimatedMergeSize) + Comparator.comparingLong(MergeTask::estimatedRemainingMergeSize) ); for (int i = 0; i < mergeTaskCount; i++) { MergeTask mergeTask = mock(MergeTask.class); when(mergeTask.supportsIOThrottling()).thenReturn(randomBoolean()); // merge tasks of various sizes (0 might be a valid value) - when(mergeTask.estimatedMergeSize()).thenReturn(randomLongBetween(0, 10)); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(randomLongBetween(0, 10)); doAnswer(mock -> { // each individual merge task can either "run" or be "backlogged" at any point in time Schedule schedule = randomFrom(Schedule.values()); @@ -650,7 +734,10 @@ public void testMergeTasksExecuteInSizeOrder() { } if (schedule == RUN && mergeTasksAvailableToRun.isEmpty() == false) { // assert the merge task that's now going to run is the smallest of the ones currently available to run - assertThat(mergeTask.estimatedMergeSize(), lessThanOrEqualTo(mergeTasksAvailableToRun.peek().estimatedMergeSize())); + assertThat( + mergeTask.estimatedRemainingMergeSize(), + lessThanOrEqualTo(mergeTasksAvailableToRun.peek().estimatedRemainingMergeSize()) + ); } return schedule; }).when(mergeTask).schedule(); @@ -675,6 +762,123 @@ public void testMergeTasksExecuteInSizeOrder() { } } + public void testMergeTaskQueueAvailableBudgetTracking() throws Exception { + MergeTaskPriorityBlockingQueue mergeTaskPriorityBlockingQueue = new MergeTaskPriorityBlockingQueue(); + assertThat(mergeTaskPriorityBlockingQueue.getAvailableBudget(), is(0L)); + long availableBudget = randomLongBetween(1, 10); + mergeTaskPriorityBlockingQueue.updateBudget(availableBudget); + assertThat(mergeTaskPriorityBlockingQueue.getAvailableBudget(), is(availableBudget)); + + int taskCount = randomIntBetween(5, 15); + for (int i = 0; i < taskCount; i++) { + MergeTask mergeTask = mock(MergeTask.class); + when(mergeTask.estimatedRemainingMergeSize()).thenReturn(randomLongBetween(1, 10)); + mergeTaskPriorityBlockingQueue.enqueue(mergeTask); + } + assertThat(mergeTaskPriorityBlockingQueue.queueSize(), is(taskCount)); + assertThat(mergeTaskPriorityBlockingQueue.getAvailableBudget(), is(availableBudget)); + + List.ElementWithReleasableBudget> tookElements = new ArrayList<>(); + + while (mergeTaskPriorityBlockingQueue.isQueueEmpty() == false) { + if (mergeTaskPriorityBlockingQueue.peekQueue().estimatedRemainingMergeSize() <= mergeTaskPriorityBlockingQueue + .getAvailableBudget() && randomBoolean()) { + // take another element (merge task) from the queue + long prevBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + tookElements.add(mergeTaskPriorityBlockingQueue.take()); + long afterBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + assertThat(afterBudget, greaterThanOrEqualTo(0L)); + assertThat(prevBudget - afterBudget, is(tookElements.getLast().element().estimatedRemainingMergeSize())); + } else if (tookElements.stream().anyMatch(e -> e.isClosed() == false) && randomBoolean()) { + // "closes" a previously took element to simulate it has gone out of scope + int index = randomValueOtherThanMany( + i -> tookElements.get(i).isClosed(), + () -> randomIntBetween(0, tookElements.size() - 1) + ); + var elementToClose = tookElements.remove(index); + long prevBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + elementToClose.close(); + long afterBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + // budget hasn't yet changed, the update budget method needs to be invoked before it does + assertThat(afterBudget, is(prevBudget)); + } else if (randomBoolean()) { + // update (possibly increment) the available budget + long budgetIncrement = randomLongBetween(0, 3); + availableBudget += budgetIncrement; + mergeTaskPriorityBlockingQueue.updateBudget(availableBudget); + // "closed" took elements should not impact budget computation + tookElements.removeIf(PriorityBlockingQueueWithBudget.ElementWithReleasableBudget::isClosed); + long expectedBudget = availableBudget - tookElements.stream() + .mapToLong(e -> e.element().estimatedRemainingMergeSize()) + .sum(); + long afterBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + assertThat(afterBudget, is(expectedBudget)); + } + } + } + + public void testMergeTaskQueueBudgetTrackingWhenEstimatedRemainingMergeSizeChanges() throws Exception { + MergeTaskPriorityBlockingQueue mergeTaskPriorityBlockingQueue = new MergeTaskPriorityBlockingQueue(); + assertThat(mergeTaskPriorityBlockingQueue.getAvailableBudget(), is(0L)); + // plenty of available budget (this should be fixed for this test) + final long availableBudget = randomLongBetween(1000L, 2000L); + mergeTaskPriorityBlockingQueue.updateBudget(availableBudget); + assertThat(mergeTaskPriorityBlockingQueue.getAvailableBudget(), is(availableBudget)); + + IdentityHashMap budgetMap = new IdentityHashMap<>(); + int taskCount = randomIntBetween(5, 15); + for (int i = 0; i < taskCount; i++) { + MergeTask mergeTask = mock(MergeTask.class); + budgetMap.put(mergeTask, randomLongBetween(1L, 10L)); + doAnswer(invocation -> budgetMap.get((MergeTask) invocation.getMock())).when(mergeTask).estimatedRemainingMergeSize(); + mergeTaskPriorityBlockingQueue.enqueue(mergeTask); + } + assertThat(mergeTaskPriorityBlockingQueue.queueSize(), is(taskCount)); + assertThat(mergeTaskPriorityBlockingQueue.getAvailableBudget(), is(availableBudget)); + + List.ElementWithReleasableBudget> tookElements = new ArrayList<>(); + + while (mergeTaskPriorityBlockingQueue.isQueueEmpty() == false) { + if (tookElements.stream().allMatch(PriorityBlockingQueueWithBudget.ElementWithReleasableBudget::isClosed) || randomBoolean()) { + // take another element (merge task) from the queue + long prevBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + tookElements.add(mergeTaskPriorityBlockingQueue.take()); + long afterBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + assertThat(afterBudget, greaterThanOrEqualTo(0L)); + assertThat(prevBudget - afterBudget, is(tookElements.getLast().element().estimatedRemainingMergeSize())); + } else if (randomBoolean()) { + // "closes" a previously took element to simulate it has gone out of scope + int index = randomValueOtherThanMany( + i -> tookElements.get(i).isClosed(), + () -> randomIntBetween(0, tookElements.size() - 1) + ); + var elementToClose = tookElements.remove(index); + long prevBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + elementToClose.close(); + long afterBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + // budget hasn't yet changed, the update budget method needs to be invoked before it does + assertThat(afterBudget, is(prevBudget)); + } else { + // update the remaining merge size of a took (but not "closed") merge task + int index = randomValueOtherThanMany( + i -> tookElements.get(i).isClosed(), + () -> randomIntBetween(0, tookElements.size() - 1) + ); + var elementToUpdate = tookElements.get(index); + long prevElementBudget = elementToUpdate.element().estimatedRemainingMergeSize(); + long afterElementBudget = randomValueOtherThan(prevElementBudget, () -> randomLongBetween(1L, 10L)); + budgetMap.put(elementToUpdate.element(), afterElementBudget); + assertThat(elementToUpdate.element().estimatedRemainingMergeSize(), is(afterElementBudget)); + // "closed" took elements should not impact budget computation + tookElements.removeIf(PriorityBlockingQueueWithBudget.ElementWithReleasableBudget::isClosed); + long expectedBudget = availableBudget - tookElements.stream().mapToLong(e -> budgetMap.get(e.element())).sum(); + mergeTaskPriorityBlockingQueue.updateBudget(availableBudget); + long afterBudget = mergeTaskPriorityBlockingQueue.getAvailableBudget(); + assertThat(afterBudget, is(expectedBudget)); + } + } + } + private static class CountingMergeEventListener implements MergeEventListener { AtomicInteger queued = new AtomicInteger(); AtomicInteger aborted = new AtomicInteger(); @@ -696,14 +900,13 @@ public void onMergeAborted(OnGoingMerge merge) { } } - static ThreadPoolMergeExecutorService getThreadPoolMergeExecutorService(ThreadPool threadPool) { + static ThreadPoolMergeExecutorService getThreadPoolMergeExecutorService( + ThreadPool threadPool, + Settings settings, + NodeEnvironment nodeEnvironment + ) { ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorService - .maybeCreateThreadPoolMergeExecutorService( - threadPool, - randomBoolean() - ? Settings.EMPTY - : Settings.builder().put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), true).build() - ); + .maybeCreateThreadPoolMergeExecutorService(threadPool, ClusterSettings.createBuiltInClusterSettings(settings), nodeEnvironment); assertNotNull(threadPoolMergeExecutorService); assertTrue(threadPoolMergeExecutorService.allDone()); return threadPoolMergeExecutorService; diff --git a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java index 01a6150fd140c..156dcf581ec9c 100644 --- a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java +++ b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.DeterministicTaskQueue; import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.MergeSchedulerConfig; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler.MergeTask; @@ -26,6 +27,7 @@ import org.elasticsearch.test.IndexSettingsModule; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; +import org.junit.After; import org.mockito.ArgumentCaptor; import java.io.IOException; @@ -53,10 +55,25 @@ public class ThreadPoolMergeSchedulerTests extends ESTestCase { + private NodeEnvironment nodeEnvironment; + + @After + public void closeNodeEnv() { + if (nodeEnvironment != null) { + nodeEnvironment.close(); + nodeEnvironment = null; + } + } + public void testMergesExecuteInSizeOrder() throws IOException { DeterministicTaskQueue threadPoolTaskQueue = new DeterministicTaskQueue(); + Settings settings = Settings.builder() + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") + .build(); + nodeEnvironment = newNodeEnvironment(settings); ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorServiceTests - .getThreadPoolMergeExecutorService(threadPoolTaskQueue.getThreadPool()); + .getThreadPoolMergeExecutorService(threadPoolTaskQueue.getThreadPool(), settings, nodeEnvironment); try ( ThreadPoolMergeScheduler threadPoolMergeScheduler = new ThreadPoolMergeScheduler( new ShardId("index", "_na_", 1), @@ -142,7 +159,10 @@ public void testSimpleMergeTaskReEnqueueingBySize() { merge -> 0 ); // sort backlogged merges by size - PriorityQueue backloggedMergeTasks = new PriorityQueue<>(16, Comparator.comparingLong(MergeTask::estimatedMergeSize)); + PriorityQueue backloggedMergeTasks = new PriorityQueue<>( + 16, + Comparator.comparingLong(MergeTask::estimatedRemainingMergeSize) + ); // more merge tasks than merge threads int mergeCount = mergeExecutorThreadCount + randomIntBetween(2, 10); for (int i = 0; i < mergeCount; i++) { @@ -341,10 +361,13 @@ public void testMergeSourceWithFollowUpMergesRunSequentially() throws Exception Settings settings = Settings.builder() .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) .put(MergeSchedulerConfig.MAX_THREAD_COUNT_SETTING.getKey(), mergeExecutorThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorServiceTests - .getThreadPoolMergeExecutorService(testThreadPool); + .getThreadPoolMergeExecutorService(testThreadPool, settings, nodeEnvironment); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); try ( ThreadPoolMergeScheduler threadPoolMergeScheduler = new ThreadPoolMergeScheduler( @@ -414,10 +437,13 @@ public void testMergesRunConcurrently() throws Exception { Settings settings = Settings.builder() .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) .put(MergeSchedulerConfig.MAX_THREAD_COUNT_SETTING.getKey(), mergeSchedulerMaxThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorServiceTests - .getThreadPoolMergeExecutorService(testThreadPool); + .getThreadPoolMergeExecutorService(testThreadPool, settings, nodeEnvironment); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) testThreadPool.executor(ThreadPool.Names.MERGE); try ( @@ -460,7 +486,7 @@ public void testMergesRunConcurrently() throws Exception { // also check the same for the thread-pool executor assertThat(threadPoolMergeExecutorService.getRunningMergeTasks().size(), is(mergeSchedulerMaxThreadCount)); // queued merge tasks do not include backlogged merges - assertThat(threadPoolMergeExecutorService.getQueuedMergeTasks().size(), is(0)); + assertThat(threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(0)); // also check thread-pool stats for the same // there are active thread-pool threads waiting for the backlogged merge tasks to be re-enqueued int activeMergeThreads = Math.min(mergeCount - finalCompletedMergesCount, mergeExecutorThreadCount); @@ -481,7 +507,7 @@ public void testMergesRunConcurrently() throws Exception { // also check thread-pool executor for the same assertThat(threadPoolMergeExecutorService.getRunningMergeTasks().size(), is(finalRemainingMergesCount)); // no more backlogged merges - assertThat(threadPoolMergeExecutorService.getQueuedMergeTasks().size(), is(0)); + assertThat(threadPoolMergeExecutorService.getMergeTasksQueueLength(), is(0)); // also check thread-pool stats for the same assertThat(threadPoolExecutor.getActiveCount(), is(finalRemainingMergesCount)); assertThat(threadPoolExecutor.getQueue().size(), is(0)); @@ -500,10 +526,13 @@ public void testSchedulerCloseWaitsForRunningMerge() throws Exception { Settings settings = Settings.builder() .put(EsExecutors.NODE_PROCESSORS_SETTING.getKey(), mergeExecutorThreadCount) .put(MergeSchedulerConfig.MAX_THREAD_COUNT_SETTING.getKey(), mergeSchedulerMaxThreadCount) + // disable fs available disk space feature for this test + .put(ThreadPoolMergeExecutorService.INDICES_MERGE_DISK_CHECK_INTERVAL_SETTING.getKey(), "0s") .build(); + nodeEnvironment = newNodeEnvironment(settings); try (TestThreadPool testThreadPool = new TestThreadPool("test", settings)) { ThreadPoolMergeExecutorService threadPoolMergeExecutorService = ThreadPoolMergeExecutorServiceTests - .getThreadPoolMergeExecutorService(testThreadPool); + .getThreadPoolMergeExecutorService(testThreadPool, settings, nodeEnvironment); assertThat(threadPoolMergeExecutorService.getMaxConcurrentMerges(), equalTo(mergeExecutorThreadCount)); try ( ThreadPoolMergeScheduler threadPoolMergeScheduler = new ThreadPoolMergeScheduler( diff --git a/server/src/test/java/org/elasticsearch/index/shard/RefreshListenersTests.java b/server/src/test/java/org/elasticsearch/index/shard/RefreshListenersTests.java index 699b5e93d79f9..e21b046d7a9f8 100644 --- a/server/src/test/java/org/elasticsearch/index/shard/RefreshListenersTests.java +++ b/server/src/test/java/org/elasticsearch/index/shard/RefreshListenersTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.lucene.uid.Versions; import org.elasticsearch.common.metrics.MeanMetric; +import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.concurrent.EsExecutors; @@ -33,6 +34,7 @@ import org.elasticsearch.core.Releasable; import org.elasticsearch.core.Strings; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexSettings; @@ -91,6 +93,7 @@ public class RefreshListenersTests extends ESTestCase { private Engine engine; private volatile int maxListeners; private ThreadPool threadPool; + private NodeEnvironment nodeEnvironment; private ThreadPoolMergeExecutorService threadPoolMergeExecutorService; private Store store; @@ -104,7 +107,12 @@ public void setupListeners() throws Exception { .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), randomBoolean()) .build(); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("index", settings); - threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService(threadPool, settings); + nodeEnvironment = newNodeEnvironment(settings); + threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( + threadPool, + ClusterSettings.createBuiltInClusterSettings(settings), + nodeEnvironment + ); listeners = new RefreshListeners( () -> maxListeners, () -> engine.refresh("too-many-listeners"), @@ -178,8 +186,7 @@ public void onFailedEngine(String reason, @Nullable Exception e) { @After public void tearDownListeners() throws Exception { - IOUtils.close(engine, store); - terminate(threadPool); + IOUtils.close(engine, store, nodeEnvironment, () -> terminate(threadPool)); } public void testBeforeRefresh() throws Exception { diff --git a/test/framework/src/main/java/org/elasticsearch/index/engine/EngineTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/engine/EngineTestCase.java index 9bb6696b1ee6d..3a134a3d0d9e5 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/engine/EngineTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/engine/EngineTestCase.java @@ -61,6 +61,7 @@ import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.lucene.uid.Versions; +import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; @@ -68,6 +69,7 @@ import org.elasticsearch.core.IOUtils; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexSettings; @@ -156,6 +158,7 @@ public abstract class EngineTestCase extends ESTestCase { protected static final IndexSettings INDEX_SETTINGS = IndexSettingsModule.newIndexSettings("index", Settings.EMPTY); protected ThreadPool threadPool; + protected NodeEnvironment nodeEnvironment; protected ThreadPoolMergeExecutorService threadPoolMergeExecutorService; protected TranslogHandler translogHandler; @@ -246,9 +249,11 @@ public void setUp() throws Exception { } defaultSettings = IndexSettingsModule.newIndexSettings("index", indexSettings()); threadPool = new TestThreadPool(getClass().getName()); + nodeEnvironment = newNodeEnvironment(defaultSettings.getNodeSettings()); threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( threadPool, - defaultSettings.getNodeSettings() + ClusterSettings.createBuiltInClusterSettings(defaultSettings.getNodeSettings()), + nodeEnvironment ); store = createStore(); @@ -400,7 +405,7 @@ public void tearDown() throws Exception { assertAtMostOneLuceneDocumentPerSequenceNumber(replicaEngine); } } finally { - IOUtils.close(replicaEngine, storeReplica, engine, store, () -> terminate(threadPool)); + IOUtils.close(replicaEngine, storeReplica, engine, store, () -> terminate(threadPool), nodeEnvironment); } } diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java index 89ce1f4eb06cd..47d9520c5aabb 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java @@ -154,6 +154,7 @@ public void onRecoveryFailure(RecoveryFailedException e, boolean sendShardFailur }; protected ThreadPool threadPool; + protected NodeEnvironment nodeEnvironment; protected ThreadPoolMergeExecutorService threadPoolMergeExecutorService; protected Executor writeExecutor; protected long primaryTerm; @@ -171,7 +172,12 @@ public void setUp() throws Exception { super.setUp(); Settings settings = threadPoolSettings(); threadPool = setUpThreadPool(settings); - threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService(threadPool, settings); + nodeEnvironment = newNodeEnvironment(settings); + threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( + threadPool, + ClusterSettings.createBuiltInClusterSettings(settings), + nodeEnvironment + ); writeExecutor = threadPool.executor(ThreadPool.Names.WRITE); primaryTerm = randomIntBetween(1, 100); // use random but fixed term for creating shards failOnShardFailures(); @@ -184,7 +190,7 @@ protected ThreadPool setUpThreadPool(Settings settings) { @Override public void tearDown() throws Exception { try { - tearDownThreadPool(); + IOUtils.close(nodeEnvironment, this::tearDownThreadPool); } finally { super.tearDown(); } diff --git a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java index 64d94f611f7b2..5e7481602a77a 100644 --- a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java +++ b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/index/engine/FollowingEngineTests.java @@ -18,10 +18,13 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.core.IOUtils; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexMode; import org.elasticsearch.index.IndexModule; @@ -85,6 +88,7 @@ public class FollowingEngineTests extends ESTestCase { private ThreadPool threadPool; + private NodeEnvironment nodeEnvironment; private ThreadPoolMergeExecutorService threadPoolMergeExecutorService; private Index index; private ShardId shardId; @@ -99,7 +103,12 @@ public void setUp() throws Exception { .put(ThreadPoolMergeScheduler.USE_THREAD_POOL_MERGE_SCHEDULER_SETTING.getKey(), randomBoolean()) .build(); threadPool = new TestThreadPool("following-engine-tests", settings); - threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService(threadPool, settings); + nodeEnvironment = newNodeEnvironment(settings); + threadPoolMergeExecutorService = ThreadPoolMergeExecutorService.maybeCreateThreadPoolMergeExecutorService( + threadPool, + ClusterSettings.createBuiltInClusterSettings(settings), + nodeEnvironment + ); index = new Index("index", "uuid"); shardId = new ShardId(index, 0); primaryTerm.set(randomLongBetween(1, Long.MAX_VALUE)); @@ -108,7 +117,7 @@ public void setUp() throws Exception { @Override public void tearDown() throws Exception { - terminate(threadPool); + IOUtils.close(nodeEnvironment, () -> terminate(threadPool)); super.tearDown(); } From 713ab42ac48fd58ebb6a8c888c64b0596333291b Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Mon, 9 Jun 2025 12:01:41 +0100 Subject: [PATCH 019/102] Add option to include or exclude vectors from _source retrieval (#128735) This PR introduces a new include_vectors option to the _source retrieval context. When set to false, vectors are excluded from the returned _source. This is especially efficient when used with synthetic source, as it avoids loading vector fields entirely. By default, vectors remain included unless explicitly excluded. --- docs/changelog/128735.yaml | 5 + .../230_source_exclude_vectors.yml | 225 ++++++++++++++++++ .../org/elasticsearch/TransportVersions.java | 3 +- .../index/mapper/MappedFieldType.java | 9 + .../vectors/DenseVectorFieldMapper.java | 5 + .../vectors/SparseVectorFieldMapper.java | 5 + .../action/search/SearchCapabilities.java | 3 +- .../search/fetch/FetchContext.java | 4 +- .../search/fetch/FetchPhase.java | 80 ++++++- .../fetch/subphase/FetchSourceContext.java | 76 +++++- .../subphase/FetchSourceContextTests.java | 23 +- .../mapper/RankVectorsFieldMapper.java | 5 + 12 files changed, 425 insertions(+), 18 deletions(-) create mode 100644 docs/changelog/128735.yaml create mode 100644 rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/230_source_exclude_vectors.yml diff --git a/docs/changelog/128735.yaml b/docs/changelog/128735.yaml new file mode 100644 index 0000000000000..33ea2e4e97d91 --- /dev/null +++ b/docs/changelog/128735.yaml @@ -0,0 +1,5 @@ +pr: 128735 +summary: Add option to include or exclude vectors from `_source` retrieval +area: Vector Search +type: feature +issues: [] diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/230_source_exclude_vectors.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/230_source_exclude_vectors.yml new file mode 100644 index 0000000000000..f8c3835ac6b31 --- /dev/null +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/230_source_exclude_vectors.yml @@ -0,0 +1,225 @@ +setup: + - requires: + reason: 'exclude_vectors option is required' + test_runner_features: [ capabilities ] + capabilities: + - method: GET + path: /_search + capabilities: [ exclude_vectors_param ] + - skip: + features: "headers" + + - do: + indices.create: + index: test + body: + mappings: + properties: + name: + type: keyword + sparse_vector: + type: sparse_vector + vector: + type: dense_vector + dims: 5 + similarity: l2_norm + + nested: + type: nested + properties: + paragraph_id: + type: keyword + vector: + type: dense_vector + dims: 5 + similarity: l2_norm + sparse_vector: + type: sparse_vector + + - do: + index: + index: test + id: "1" + body: + name: cow.jpg + vector: [36, 267, -311, 12, -202] + + - do: + index: + index: test + id: "2" + body: + name: moose.jpg + nested: + - paragraph_id: 0 + vector: [-0.5, 100.0, -13, 14.8, -156.0] + - paragraph_id: 2 + vector: [0, 100.0, 0, 14.8, -156.0] + - paragraph_id: 3 + vector: [0, 1.0, 0, 1.8, -15.0] + + - do: + index: + index: test + id: "3" + body: + name: rabbit.jpg + vector: [-0.5, 100.0, -13, 14.8, -156.0] + sparse_vector: + running: 3 + good: 17 + run: 22 + + - do: + index: + index: test + id: "4" + body: + name: zoolander.jpg + nested: + - paragraph_id: 0 + vector: [ -0.5, 100.0, -13, 14.8, -156.0 ] + sparse_vector: + running: 3 + good: 17 + run: 22 + - paragraph_id: 1 + sparse_vector: + modeling: 32 + model: 20 + mode: 54 + - paragraph_id: 2 + vector: [ -9.8, 109, 32, 14.8, 23 ] + + + - do: + indices.refresh: {} + +--- +"exclude vectors": + - do: + search: + index: test + body: + _source: + exclude_vectors: true + sort: ["name"] + + - match: { hits.hits.0._id: "1"} + - match: { hits.hits.0._source.name: "cow.jpg"} + - not_exists: hits.hits.0._source.vector + + - match: { hits.hits.1._id: "2"} + - match: { hits.hits.1._source.name: "moose.jpg"} + - length: { hits.hits.1._source.nested: 3 } + - not_exists: hits.hits.1._source.nested.0.vector + - match: { hits.hits.1._source.nested.0.paragraph_id: 0 } + - not_exists: hits.hits.1._source.nested.1.vector + - match: { hits.hits.1._source.nested.1.paragraph_id: 2 } + - not_exists: hits.hits.1._source.nested.2.vector + - match: { hits.hits.1._source.nested.2.paragraph_id: 3 } + + - match: { hits.hits.2._id: "3" } + - match: { hits.hits.2._source.name: "rabbit.jpg" } + - not_exists: hits.hits.2._source.vector + - not_exists: hits.hits.2._source.sparse_vector + + - match: { hits.hits.3._id: "4" } + - match: { hits.hits.3._source.name: "zoolander.jpg" } + - length: { hits.hits.3._source.nested: 3 } + - not_exists: hits.hits.3._source.nested.0.vector + - not_exists: hits.hits.3._source.nested.0.sparse_vector + - match: { hits.hits.3._source.nested.0.paragraph_id: 0 } + - not_exists: hits.hits.3._source.nested.1.sparse_vector + - match: { hits.hits.3._source.nested.1.paragraph_id: 1 } + - not_exists: hits.hits.3._source.nested.2.vector + - match: { hits.hits.3._source.nested.2.paragraph_id: 2 } + +--- +"include vectors": + - do: + search: + index: test + body: + _source: + exclude_vectors: false + sort: ["name"] + + - match: { hits.hits.0._id: "1"} + - match: { hits.hits.0._source.name: "cow.jpg"} + - exists: hits.hits.0._source.vector + + - match: { hits.hits.1._id: "2"} + - match: { hits.hits.1._source.name: "moose.jpg"} + - length: { hits.hits.1._source.nested: 3 } + - exists: hits.hits.1._source.nested.0.vector + - match: { hits.hits.1._source.nested.0.paragraph_id: 0 } + - exists: hits.hits.1._source.nested.1.vector + - match: { hits.hits.1._source.nested.1.paragraph_id: 2 } + - exists: hits.hits.1._source.nested.2.vector + - match: { hits.hits.1._source.nested.2.paragraph_id: 3 } + + - match: { hits.hits.2._id: "3" } + - match: { hits.hits.2._source.name: "rabbit.jpg" } + - exists: hits.hits.2._source.vector + - exists: hits.hits.2._source.sparse_vector + + - match: { hits.hits.3._id: "4" } + - match: { hits.hits.3._source.name: "zoolander.jpg" } + - length: { hits.hits.3._source.nested: 3 } + - exists: hits.hits.3._source.nested.0.vector + - exists: hits.hits.3._source.nested.0.sparse_vector + - match: { hits.hits.3._source.nested.0.paragraph_id: 0 } + - exists: hits.hits.3._source.nested.1.sparse_vector + - match: { hits.hits.3._source.nested.1.paragraph_id: 1 } + - exists: hits.hits.3._source.nested.2.vector + - match: { hits.hits.3._source.nested.2.paragraph_id: 2 } + +--- +"exclude vectors with fields": + - do: + search: + index: test + body: + _source: + exclude_vectors: true + sort: ["name"] + fields: [vector, sparse_vector, nested.*] + + - match: { hits.hits.0._id: "1"} + - match: { hits.hits.0._source.name: "cow.jpg"} + - not_exists: hits.hits.0._source.vector + - exists: hits.hits.0.fields.vector + + - match: { hits.hits.1._id: "2"} + - match: { hits.hits.1._source.name: "moose.jpg"} + - length: { hits.hits.1._source.nested: 3 } + - not_exists: hits.hits.1._source.nested.0.vector + - match: { hits.hits.1._source.nested.0.paragraph_id: 0 } + - not_exists: hits.hits.1._source.nested.1.vector + - match: { hits.hits.1._source.nested.1.paragraph_id: 2 } + - not_exists: hits.hits.1._source.nested.2.vector + - match: { hits.hits.1._source.nested.2.paragraph_id: 3 } + + - match: { hits.hits.2._id: "3" } + - match: { hits.hits.2._source.name: "rabbit.jpg" } + - not_exists: hits.hits.2._source.vector + - exists: hits.hits.2.fields.vector + - not_exists: hits.hits.2._source.sparse_vector + - exists: hits.hits.2.fields.sparse_vector + + + - match: { hits.hits.3._id: "4" } + - match: { hits.hits.3._source.name: "zoolander.jpg" } + - length: { hits.hits.3._source.nested: 3 } + - not_exists: hits.hits.3._source.nested.0.vector + - exists: hits.hits.3.fields.nested.0.vector + - not_exists: hits.hits.3._source.nested.0.sparse_vector + - match: { hits.hits.3._source.nested.0.paragraph_id: 0 } + - exists: hits.hits.3.fields.nested.0.sparse_vector + - not_exists: hits.hits.3._source.nested.1.sparse_vector + - match: { hits.hits.3._source.nested.1.paragraph_id: 1 } + - exists: hits.hits.3.fields.nested.1.sparse_vector + - not_exists: hits.hits.3._source.nested.2.vector + - match: { hits.hits.3._source.nested.2.paragraph_id: 2 } + - exists: hits.hits.3.fields.nested.2.vector diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index d83a4992f97b0..40a5d851ace98 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -191,6 +191,7 @@ static TransportVersion def(int id) { public static final TransportVersion ILM_ADD_SKIP_SETTING_8_19 = def(8_841_0_43); public static final TransportVersion ESQL_REGEX_MATCH_WITH_CASE_INSENSITIVITY_8_19 = def(8_841_0_44); public static final TransportVersion ESQL_QUERY_PLANNING_DURATION_8_19 = def(8_841_0_45); + public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM_8_19 = def(8_841_0_46); public static final TransportVersion V_9_0_0 = def(9_000_0_09); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_1 = def(9_000_0_10); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_2 = def(9_000_0_11); @@ -286,7 +287,7 @@ static TransportVersion def(int id) { public static final TransportVersion ILM_ADD_SKIP_SETTING = def(9_089_0_00); public static final TransportVersion ML_INFERENCE_MISTRAL_CHAT_COMPLETION_ADDED = def(9_090_0_00); public static final TransportVersion IDP_CUSTOM_SAML_ATTRIBUTES_ALLOW_LIST = def(9_091_0_00); - + public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM = def(9_092_0_00); /* * STOP! READ THIS FIRST! No, really, * ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _ diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java index 8b07f4de6170e..a9e67be4085dd 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java @@ -195,6 +195,15 @@ public boolean isDimension() { return false; } + /** + * Vector embeddings are typically large and not intended for human consumption, so such fields may be excluded from responses. + * + * @return true if this field contains vector embeddings. + */ + public boolean isVectorEmbedding() { + return false; + } + /** * @return true if field has script values. */ diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java index 0d6970fba1927..9bb35bdfe2f9d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java @@ -2303,6 +2303,11 @@ public boolean isAggregatable() { return false; } + @Override + public boolean isVectorEmbedding() { + return true; + } + @Override public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext) { return elementType.fielddataBuilder(this, fieldDataContext); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java index 0aeb3495608d6..9672466327247 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java @@ -121,6 +121,11 @@ public String typeName() { return CONTENT_TYPE; } + @Override + public boolean isVectorEmbedding() { + return true; + } + @Override public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext) { throw new IllegalArgumentException("[sparse_vector] fields do not support sorting, scripting or aggregating"); diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java b/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java index 8b6b48cb5b077..859605a5d6a96 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/SearchCapabilities.java @@ -49,8 +49,8 @@ private SearchCapabilities() {} private static final String INDEX_SELECTOR_SYNTAX = "index_expression_selectors"; private static final String SIGNIFICANT_TERMS_BACKGROUND_FILTER_AS_SUB = "significant_terms_background_filter_as_sub"; - private static final String SIGNIFICANT_TERMS_ON_NESTED_FIELDS = "significant_terms_on_nested_fields"; + private static final String EXCLUDE_VECTORS_PARAM = "exclude_vectors_param"; public static final Set CAPABILITIES; static { @@ -72,6 +72,7 @@ private SearchCapabilities() {} capabilities.add(INDEX_SELECTOR_SYNTAX); capabilities.add(SIGNIFICANT_TERMS_BACKGROUND_FILTER_AS_SUB); capabilities.add(SIGNIFICANT_TERMS_ON_NESTED_FIELDS); + capabilities.add(EXCLUDE_VECTORS_PARAM); CAPABILITIES = Set.copyOf(capabilities); } } diff --git a/server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java b/server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java index 4801c53ec0f1e..6b16f8687ae31 100644 --- a/server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java +++ b/server/src/main/java/org/elasticsearch/search/fetch/FetchContext.java @@ -68,7 +68,9 @@ private static FetchSourceContext buildFetchSourceContext(SearchContext in) { if (sfc != null && sfc.fetchFields()) { for (String field : sfc.fieldNames()) { if (SourceFieldMapper.NAME.equals(field)) { - fsc = fsc == null ? FetchSourceContext.of(true) : FetchSourceContext.of(true, fsc.includes(), fsc.excludes()); + fsc = fsc == null + ? FetchSourceContext.of(true) + : FetchSourceContext.of(true, fsc.excludeVectors(), fsc.includes(), fsc.excludes()); } } } diff --git a/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java b/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java index d9a8ee72b47c0..0dd9cc3622fae 100644 --- a/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java +++ b/server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java @@ -13,10 +13,13 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.TotalHits; +import org.apache.lucene.util.automaton.CharacterRunAutomaton; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.regex.Regex; import org.elasticsearch.index.fieldvisitor.LeafStoredFieldLoader; import org.elasticsearch.index.fieldvisitor.StoredFieldLoader; import org.elasticsearch.index.mapper.IdLoader; +import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.SourceLoader; import org.elasticsearch.search.LeafNestedDocuments; import org.elasticsearch.search.NestedDocuments; @@ -25,10 +28,12 @@ import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.fetch.FetchSubPhase.HitContext; +import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.InnerHitsContext; import org.elasticsearch.search.fetch.subphase.InnerHitsPhase; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.lookup.Source; +import org.elasticsearch.search.lookup.SourceFilter; import org.elasticsearch.search.lookup.SourceProvider; import org.elasticsearch.search.profile.ProfileResult; import org.elasticsearch.search.profile.Profilers; @@ -45,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * Fetch phase of a search request, used to fetch the actual top matching documents to be returned to the client, identified @@ -111,7 +117,13 @@ public Source getSource(LeafReaderContext ctx, int doc) { } private SearchHits buildSearchHits(SearchContext context, int[] docIdsToLoad, Profiler profiler, RankDocShardInfo rankDocs) { - SourceLoader sourceLoader = context.newSourceLoader(null); + // Optionally remove sparse and dense vector fields early to: + // - Reduce the in-memory size of the source + // - Speed up retrieval of the synthetic source + // Note: These vectors will no longer be accessible via _source for any sub-fetch processors, + // but they are typically accessed through doc values instead (e.g: re-scorer). + SourceFilter sourceFilter = maybeExcludeNonSemanticTextVectorFields(context); + SourceLoader sourceLoader = context.newSourceLoader(sourceFilter); FetchContext fetchContext = new FetchContext(context, sourceLoader); PreloadedSourceProvider sourceProvider = new PreloadedSourceProvider(); @@ -432,4 +444,70 @@ public String toString() { } }; } + + /** + * Determines whether vector fields should be excluded from the source based on the {@link FetchSourceContext}. + * Returns {@code true} if vector fields are explicitly marked to be excluded and {@code false} otherwise. + */ + private static boolean shouldExcludeVectorsFromSource(SearchContext context) { + if (context.fetchSourceContext() == null) { + return false; + } + return context.fetchSourceContext().excludeVectors() != null && context.fetchSourceContext().excludeVectors(); + } + + /** + * Returns a {@link SourceFilter} that excludes vector fields not associated with semantic text fields, + * unless vectors are explicitly requested to be included in the source. + * Returns {@code null} when vectors should not be filtered out. + */ + private static SourceFilter maybeExcludeNonSemanticTextVectorFields(SearchContext context) { + if (shouldExcludeVectorsFromSource(context) == false) { + return null; + } + var lookup = context.getSearchExecutionContext().getMappingLookup(); + var fetchFieldsAut = context.fetchFieldsContext() != null && context.fetchFieldsContext().fields().size() > 0 + ? new CharacterRunAutomaton( + Regex.simpleMatchToAutomaton(context.fetchFieldsContext().fields().stream().map(f -> f.field).toArray(String[]::new)) + ) + : null; + var inferenceFieldsAut = lookup.inferenceFields().size() > 0 + ? new CharacterRunAutomaton( + Regex.simpleMatchToAutomaton(lookup.inferenceFields().keySet().stream().map(f -> f + "*").toArray(String[]::new)) + ) + : null; + + List lateExcludes = new ArrayList<>(); + var excludes = lookup.getFullNameToFieldType().values().stream().filter(MappedFieldType::isVectorEmbedding).filter(f -> { + // Exclude the field specified by the `fields` option + if (fetchFieldsAut != null && fetchFieldsAut.run(f.name())) { + lateExcludes.add(f.name()); + return false; + } + // Exclude vectors from semantic text fields, as they are processed separately + return inferenceFieldsAut == null || inferenceFieldsAut.run(f.name()) == false; + }).map(f -> f.name()).collect(Collectors.toList()); + + if (lateExcludes.size() > 0) { + /** + * Adds the vector field specified by the `fields` option to the excludes list of the fetch source context. + * This ensures that vector fields are available to sub-fetch phases, but excluded during the {@link FetchSourcePhase}. + */ + if (context.fetchSourceContext() != null && context.fetchSourceContext().excludes() != null) { + for (var exclude : context.fetchSourceContext().excludes()) { + lateExcludes.add(exclude); + } + } + var fetchSourceContext = context.fetchSourceContext() == null + ? FetchSourceContext.of(true, false, null, lateExcludes.toArray(String[]::new)) + : FetchSourceContext.of( + context.fetchSourceContext().fetchSource(), + context.fetchSourceContext().excludeVectors(), + context.fetchSourceContext().includes(), + lateExcludes.toArray(String[]::new) + ); + context.fetchSourceContext(fetchSourceContext); + } + return excludes.isEmpty() ? null : new SourceFilter(new String[] {}, excludes.toArray(String[]::new)); + } } diff --git a/server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourceContext.java b/server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourceContext.java index 0594fa4909783..367635a2d8c3a 100644 --- a/server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourceContext.java +++ b/server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchSourceContext.java @@ -9,6 +9,8 @@ package org.elasticsearch.search.fetch.subphase; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; @@ -27,56 +29,87 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * Context used to fetch the {@code _source}. */ public class FetchSourceContext implements Writeable, ToXContentObject { + public static final ParseField EXCLUDE_VECTORS_FIELD = new ParseField("exclude_vectors"); public static final ParseField INCLUDES_FIELD = new ParseField("includes", "include"); public static final ParseField EXCLUDES_FIELD = new ParseField("excludes", "exclude"); - public static final FetchSourceContext FETCH_SOURCE = new FetchSourceContext(true, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY); - public static final FetchSourceContext DO_NOT_FETCH_SOURCE = new FetchSourceContext(false, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY); + public static final FetchSourceContext FETCH_SOURCE = new FetchSourceContext(true, null, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY); + public static final FetchSourceContext DO_NOT_FETCH_SOURCE = new FetchSourceContext( + false, + null, + Strings.EMPTY_ARRAY, + Strings.EMPTY_ARRAY + ); private final boolean fetchSource; private final String[] includes; private final String[] excludes; + private final Boolean excludeVectors; public static FetchSourceContext of(boolean fetchSource) { return fetchSource ? FETCH_SOURCE : DO_NOT_FETCH_SOURCE; } public static FetchSourceContext of(boolean fetchSource, @Nullable String[] includes, @Nullable String[] excludes) { - if ((includes == null || includes.length == 0) && (excludes == null || excludes.length == 0)) { + return of(fetchSource, null, includes, excludes); + } + + public static FetchSourceContext of( + boolean fetchSource, + Boolean excludeVectors, + @Nullable String[] includes, + @Nullable String[] excludes + ) { + if (excludeVectors == null && (includes == null || includes.length == 0) && (excludes == null || excludes.length == 0)) { return of(fetchSource); } - return new FetchSourceContext(fetchSource, includes, excludes); + return new FetchSourceContext(fetchSource, excludeVectors, includes, excludes); + } + + private FetchSourceContext(boolean fetchSource, Boolean excludeVectors, @Nullable String[] includes, @Nullable String[] excludes) { + this.fetchSource = fetchSource; + this.excludeVectors = excludeVectors; + this.includes = includes == null ? Strings.EMPTY_ARRAY : includes; + this.excludes = excludes == null ? Strings.EMPTY_ARRAY : excludes; } public static FetchSourceContext readFrom(StreamInput in) throws IOException { final boolean fetchSource = in.readBoolean(); + final Boolean excludeVectors = isVersionCompatibleWithExcludeVectors(in.getTransportVersion()) ? in.readOptionalBoolean() : null; final String[] includes = in.readStringArray(); final String[] excludes = in.readStringArray(); - return of(fetchSource, includes, excludes); - } - - private FetchSourceContext(boolean fetchSource, @Nullable String[] includes, @Nullable String[] excludes) { - this.fetchSource = fetchSource; - this.includes = includes == null ? Strings.EMPTY_ARRAY : includes; - this.excludes = excludes == null ? Strings.EMPTY_ARRAY : excludes; + return of(fetchSource, excludeVectors, includes, excludes); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeBoolean(fetchSource); + if (isVersionCompatibleWithExcludeVectors(out.getTransportVersion())) { + out.writeOptionalBoolean(excludeVectors); + } out.writeStringArray(includes); out.writeStringArray(excludes); } + private static boolean isVersionCompatibleWithExcludeVectors(TransportVersion version) { + return version.isPatchFrom(TransportVersions.SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM_8_19) + || version.onOrAfter(TransportVersions.SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM); + } + public boolean fetchSource() { return this.fetchSource; } + public Boolean excludeVectors() { + return this.excludeVectors; + } + public String[] includes() { return this.includes; } @@ -135,6 +168,7 @@ public static FetchSourceContext fromXContent(XContentParser parser) throws IOEx XContentParser.Token token = parser.currentToken(); boolean fetchSource = true; + Boolean excludeVectors = null; String[] includes = Strings.EMPTY_ARRAY; String[] excludes = Strings.EMPTY_ARRAY; if (token == XContentParser.Token.VALUE_BOOLEAN) { @@ -169,6 +203,18 @@ public static FetchSourceContext fromXContent(XContentParser parser) throws IOEx includes = new String[] { parser.text() }; } else if (EXCLUDES_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { excludes = new String[] { parser.text() }; + } else if (EXCLUDE_VECTORS_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + excludeVectors = parser.booleanValue(); + } else { + throw new ParsingException( + parser.getTokenLocation(), + "Unknown key for a " + token + " in [" + currentFieldName + "].", + parser.getTokenLocation() + ); + } + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + if (EXCLUDE_VECTORS_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + excludeVectors = parser.booleanValue(); } else { throw new ParsingException( parser.getTokenLocation(), @@ -201,7 +247,7 @@ public static FetchSourceContext fromXContent(XContentParser parser) throws IOEx parser.getTokenLocation() ); } - return FetchSourceContext.of(fetchSource, includes, excludes); + return FetchSourceContext.of(fetchSource, excludeVectors, includes, excludes); } private static String[] parseStringArray(XContentParser parser, String currentFieldName) throws IOException { @@ -227,6 +273,9 @@ private static String[] parseStringArray(XContentParser parser, String currentFi public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { if (fetchSource) { builder.startObject(); + if (excludeVectors != null) { + builder.field(EXCLUDE_VECTORS_FIELD.getPreferredName(), excludeVectors); + } builder.array(INCLUDES_FIELD.getPreferredName(), includes); builder.array(EXCLUDES_FIELD.getPreferredName(), excludes); builder.endObject(); @@ -244,6 +293,7 @@ public boolean equals(Object o) { FetchSourceContext that = (FetchSourceContext) o; if (fetchSource != that.fetchSource) return false; + if (excludeVectors != that.excludeVectors) return false; if (Arrays.equals(excludes, that.excludes) == false) return false; if (Arrays.equals(includes, that.includes) == false) return false; @@ -252,7 +302,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = (fetchSource ? 1 : 0); + int result = Objects.hash(fetchSource, excludeVectors); result = 31 * result + Arrays.hashCode(includes); result = 31 * result + Arrays.hashCode(excludes); return result; diff --git a/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourceContextTests.java b/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourceContextTests.java index 3234f5a638680..e19567addb39f 100644 --- a/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourceContextTests.java +++ b/server/src/test/java/org/elasticsearch/search/fetch/subphase/FetchSourceContextTests.java @@ -35,6 +35,7 @@ protected Writeable.Reader instanceReader() { protected FetchSourceContext createTestInstance() { return FetchSourceContext.of( true, + randomBoolean() ? null : randomBoolean(), randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5)), randomArray(0, 5, String[]::new, () -> randomAlphaOfLength(5)) ); @@ -42,7 +43,27 @@ protected FetchSourceContext createTestInstance() { @Override protected FetchSourceContext mutateInstance(FetchSourceContext instance) { - return null;// TODO implement https://github.com/elastic/elasticsearch/issues/25929 + return switch (randomInt(2)) { + case 0 -> FetchSourceContext.of( + true, + instance.excludeVectors() != null ? instance.excludeVectors() == false : randomBoolean(), + instance.includes(), + instance.excludes() + ); + case 1 -> FetchSourceContext.of( + true, + instance.excludeVectors(), + randomArray(instance.includes().length + 1, instance.includes().length + 5, String[]::new, () -> randomAlphaOfLength(5)), + instance.excludes() + ); + case 2 -> FetchSourceContext.of( + true, + instance.excludeVectors(), + instance.includes(), + randomArray(instance.excludes().length + 1, instance.excludes().length + 5, String[]::new, () -> randomAlphaOfLength(5)) + ); + default -> throw new AssertionError("cannot reach"); + }; } public void testFromXContentException() throws IOException { diff --git a/x-pack/plugin/rank-vectors/src/main/java/org/elasticsearch/xpack/rank/vectors/mapper/RankVectorsFieldMapper.java b/x-pack/plugin/rank-vectors/src/main/java/org/elasticsearch/xpack/rank/vectors/mapper/RankVectorsFieldMapper.java index 8f2e74f3bc130..7c536640f1f95 100644 --- a/x-pack/plugin/rank-vectors/src/main/java/org/elasticsearch/xpack/rank/vectors/mapper/RankVectorsFieldMapper.java +++ b/x-pack/plugin/rank-vectors/src/main/java/org/elasticsearch/xpack/rank/vectors/mapper/RankVectorsFieldMapper.java @@ -172,6 +172,11 @@ public String typeName() { return CONTENT_TYPE; } + @Override + public boolean isVectorEmbedding() { + return true; + } + @Override public ValueFetcher valueFetcher(SearchExecutionContext context, String format) { if (format != null) { From 07765626883f1b0fd7ee818a3d55a8a6ba70b04a Mon Sep 17 00:00:00 2001 From: Mridula Date: Mon, 9 Jun 2025 12:59:27 +0100 Subject: [PATCH 020/102] Remove direct minScore propagation to inner retrievers --- .../elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java index 6e94573fe065c..436096523a1ec 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java @@ -199,7 +199,6 @@ public void doToXContent(XContentBuilder builder, Params params) throws IOExcept builder.field(LinearRetrieverComponent.WEIGHT_FIELD.getPreferredName(), weights[index]); builder.field(LinearRetrieverComponent.NORMALIZER_FIELD.getPreferredName(), normalizers[index].getName()); builder.endObject(); - entry.retriever.minScore(this.minScore); index++; } builder.endArray(); From f145d26c3056ec8421acea99fe1731ae4c96af1d Mon Sep 17 00:00:00 2001 From: Mridula Date: Mon, 9 Jun 2025 13:33:22 +0100 Subject: [PATCH 021/102] cleaned up skip --- .../rest-api-spec/test/linear/10_linear_retriever.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml index 0f3d2167c14c1..b588febe14c22 100644 --- a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml +++ b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml @@ -266,10 +266,6 @@ setup: --- "should normalize initial scores with l2_norm": - - skip: - version: "<8.19.0" - reason: "l2_norm normalization is only available from 8.19.0" - features: "l2_norm" - do: search: index: test @@ -324,10 +320,6 @@ setup: --- "should handle all zero scores in normalization": - - skip: - version: "<8.19.0" - reason: "l2_norm normalization is only available from 8.19.0" - features: "l2_norm" - do: search: index: test From d8b68974ecf411199bdfef60d94169a066c3c968 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:28:42 +1000 Subject: [PATCH 022/102] Mute org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests testAvailableDiskSpaceMonitorWhenFileSystemStatErrors #129149 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 4ab27070d12e2..ad66c8096ac47 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -513,6 +513,9 @@ tests: - class: org.elasticsearch.packaging.test.DockerTests method: test081SymlinksAreFollowedWithEnvironmentVariableFiles issue: https://github.com/elastic/elasticsearch/issues/128867 +- class: org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests + method: testAvailableDiskSpaceMonitorWhenFileSystemStatErrors + issue: https://github.com/elastic/elasticsearch/issues/129149 # Examples: # From 82c7ab126dcc19ed36aa595e7afc56a7e47392f9 Mon Sep 17 00:00:00 2001 From: Jan-Kazlouski-elastic Date: Mon, 9 Jun 2025 16:36:19 +0300 Subject: [PATCH 023/102] Add transport version for ML inference Mistral chat completion (#129033) * Add transport version for ML inference Mistral chat completion * Add changelog for Mistral Chat Completion version fix * Revert "Add changelog for Mistral Chat Completion version fix" This reverts commit 7a57416bdc65805d5303ee3ee7db3368aad89689. --- server/src/main/java/org/elasticsearch/TransportVersions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 40a5d851ace98..401dec30ab23c 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -192,6 +192,7 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_REGEX_MATCH_WITH_CASE_INSENSITIVITY_8_19 = def(8_841_0_44); public static final TransportVersion ESQL_QUERY_PLANNING_DURATION_8_19 = def(8_841_0_45); public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM_8_19 = def(8_841_0_46); + public static final TransportVersion ML_INFERENCE_MISTRAL_CHAT_COMPLETION_ADDED_8_19 = def(8_841_0_47); public static final TransportVersion V_9_0_0 = def(9_000_0_09); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_1 = def(9_000_0_10); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_2 = def(9_000_0_11); From eca383df979b960e081f872227c72b9468fa1fa1 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Mon, 9 Jun 2025 09:52:16 -0400 Subject: [PATCH 024/102] Correct index path validation (#129144) All we care about is if reindex is true or false. We shouldn't worry about force merge. Because if reindex is true, we will create the directory, if its false, we won't. --- .../java/org/elasticsearch/test/knn/KnnIndexTester.java | 9 ++------- .../main/java/org/elasticsearch/test/knn/KnnIndexer.java | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java index 6aa2e051bacc2..d3f707d191bc2 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java @@ -177,13 +177,8 @@ public static void main(String[] args) throws Exception { cmdLineArgs.vectorSpace(), cmdLineArgs.numDocs() ); - if (Files.exists(indexPath) == false) { - if (cmdLineArgs.reindex() == false) { - throw new IllegalArgumentException("Index path does not exist: " + indexPath); - } - if (cmdLineArgs.forceMerge()) { - throw new IllegalArgumentException("Force merging without an existing index in: " + indexPath); - } + if (cmdLineArgs.reindex() == false && Files.exists(indexPath) == false) { + throw new IllegalArgumentException("Index path does not exist: " + indexPath); } if (cmdLineArgs.reindex()) { knnIndexer.createIndex(result); diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java index 07ee4975df7e3..b6fc2ebd8b00e 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java @@ -304,7 +304,7 @@ private void readNext() throws IOException { bytes.position(0); // wrap around back to the start of the file if we hit the end: logger.warn("VectorReader hit EOF when reading " + this.input + "; now wrapping around to start of file again"); - this.input.position(position); + input.position(position); bytesRead = Channels.readFromFileChannel(this.input, position, bytes); if (bytesRead < bytes.capacity()) { throw new IllegalStateException( From fb6ec9a55edbfa889b5d5c0c8e658ff52da402af Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Tue, 10 Jun 2025 00:02:55 +1000 Subject: [PATCH 025/102] Mute org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests testUnavailableBudgetBlocksNewMergeTasksFromStartingExecution #129148 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index ad66c8096ac47..1d15744cb2a94 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -516,6 +516,9 @@ tests: - class: org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests method: testAvailableDiskSpaceMonitorWhenFileSystemStatErrors issue: https://github.com/elastic/elasticsearch/issues/129149 +- class: org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests + method: testUnavailableBudgetBlocksNewMergeTasksFromStartingExecution + issue: https://github.com/elastic/elasticsearch/issues/129148 # Examples: # From 6806b24d2334d0b8c116beaf3e7b5a9238cc9461 Mon Sep 17 00:00:00 2001 From: Leonardo Hoet <55866308+leo-hoet@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:31:56 -0300 Subject: [PATCH 026/102] Implemented completion task for Google VertexAI (#128694) * Google Vertex AI completion model, response entity and tests * Fixed GoogleVertexAiServiceTest for Service configuration * Changelog * Removed downcasting and using `moveToFirstToken` * Create GoogleVertexAiChatCompletionResponseHandler for streaming and non streaming responses * Added unit tests * PR feedback * Removed googlevertexaicompletion model. Using just GoogleVertexAiChatCompletionModel for completion and chat completion * Renamed uri -> nonStreamingUri. Added streamingUri and getters in GoogleVertexAiChatCompletionModel * Moved rateLimitGroupHashing to subclasses of GoogleVertexAiModel * Fixed rate limit has of GoogleVertexAiRerankModel and refactored uri for GoogleVertexAiUnifiedChatCompletionRequest --------- Co-authored-by: lhoet-google Co-authored-by: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> --- docs/changelog/128694.yaml | 5 + .../googlevertexai/GoogleVertexAiModel.java | 19 +-- .../GoogleVertexAiResponseHandler.java | 15 +++ .../GoogleVertexAiSecretSettings.java | 5 +- .../googlevertexai/GoogleVertexAiService.java | 18 +-- .../GoogleVertexAiStreamingProcessor.java | 64 ++++++++++ ...iUnifiedChatCompletionResponseHandler.java | 11 +- .../action/GoogleVertexAiActionCreator.java | 14 ++- .../action/GoogleVertexAiActionVisitor.java | 1 + .../GoogleVertexAiChatCompletionModel.java | 50 +++++++- .../GoogleVertexAiEmbeddingsModel.java | 18 ++- .../GoogleVertexAiEmbeddingsRequest.java | 4 +- .../request/GoogleVertexAiRerankRequest.java | 4 +- ...eVertexAiUnifiedChatCompletionRequest.java | 6 +- .../request/GoogleVertexAiUtils.java | 2 + .../rerank/GoogleVertexAiRerankModel.java | 18 ++- ...oogleVertexAiCompletionResponseEntity.java | 103 +++++++++++++++ .../GoogleVertexAiServiceTests.java | 12 +- ...GoogleVertexAiStreamingProcessorTests.java | 119 ++++++++++++++++++ ...texAiUnifiedChatCompletionActionTests.java | 2 +- ...oogleVertexAiChatCompletionModelTests.java | 4 +- .../GoogleVertexAiEmbeddingsModelTests.java | 2 +- ...VertexAiCompletionResponseEntityTests.java | 80 ++++++++++++ 23 files changed, 520 insertions(+), 56 deletions(-) create mode 100644 docs/changelog/128694.yaml create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessor.java create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntity.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessorTests.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntityTests.java diff --git a/docs/changelog/128694.yaml b/docs/changelog/128694.yaml new file mode 100644 index 0000000000000..031bec11899e5 --- /dev/null +++ b/docs/changelog/128694.yaml @@ -0,0 +1,5 @@ +pr: 128694 +summary: "Adding Google VertexAI completion integration" +area: Inference +type: enhancement +issues: [ ] diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiModel.java index 60cd2faa7155b..0ba69b2a34414 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiModel.java @@ -24,7 +24,7 @@ public abstract class GoogleVertexAiModel extends RateLimitGroupingModel { private final GoogleVertexAiRateLimitServiceSettings rateLimitServiceSettings; - protected URI uri; + protected URI nonStreamingUri; public GoogleVertexAiModel( ModelConfigurations configurations, @@ -39,14 +39,14 @@ public GoogleVertexAiModel( public GoogleVertexAiModel(GoogleVertexAiModel model, ServiceSettings serviceSettings) { super(model, serviceSettings); - uri = model.uri(); + nonStreamingUri = model.nonStreamingUri(); rateLimitServiceSettings = model.rateLimitServiceSettings(); } public GoogleVertexAiModel(GoogleVertexAiModel model, TaskSettings taskSettings) { super(model, taskSettings); - uri = model.uri(); + nonStreamingUri = model.nonStreamingUri(); rateLimitServiceSettings = model.rateLimitServiceSettings(); } @@ -56,17 +56,8 @@ public GoogleVertexAiRateLimitServiceSettings rateLimitServiceSettings() { return rateLimitServiceSettings; } - public URI uri() { - return uri; - } - - @Override - public int rateLimitGroupingHash() { - // In VertexAI rate limiting is scoped to the project, region and model. URI already has this information so we are using that. - // API Key does not affect the quota - // https://ai.google.dev/gemini-api/docs/rate-limits - // https://cloud.google.com/vertex-ai/docs/quotas - return Objects.hash(uri); + public URI nonStreamingUri() { + return nonStreamingUri; } @Override diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiResponseHandler.java index 423d23639bdad..671a37dda151e 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiResponseHandler.java @@ -7,14 +7,19 @@ package org.elasticsearch.xpack.inference.services.googlevertexai; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.xpack.core.inference.results.StreamingChatCompletionResults; 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.ErrorResponse; import org.elasticsearch.xpack.inference.external.http.retry.ResponseParser; import org.elasticsearch.xpack.inference.external.http.retry.RetryException; import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.external.response.streaming.ServerSentEventParser; +import org.elasticsearch.xpack.inference.external.response.streaming.ServerSentEventProcessor; import org.elasticsearch.xpack.inference.services.googlevertexai.response.GoogleVertexAiErrorResponseEntity; +import java.util.concurrent.Flow; import java.util.function.Function; import static org.elasticsearch.core.Strings.format; @@ -66,4 +71,14 @@ protected void checkForFailureStatusCode(Request request, HttpResult result) thr private static String resourceNotFoundError(Request request) { return format("Resource not found at [%s]", request.getURI()); } + + @Override + public InferenceServiceResults parseResult(Request request, Flow.Publisher flow) { + var serverSentEventProcessor = new ServerSentEventProcessor(new ServerSentEventParser()); + var googleVertexAiProcessor = new GoogleVertexAiStreamingProcessor(); + + flow.subscribe(serverSentEventProcessor); + serverSentEventProcessor.subscribe(googleVertexAiProcessor); + return new StreamingChatCompletionResults(googleVertexAiProcessor); + } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiSecretSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiSecretSettings.java index 1abf1db642932..b97fc1e483a92 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiSecretSettings.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiSecretSettings.java @@ -124,8 +124,9 @@ public static Map get() { var configurationMap = new HashMap(); configurationMap.put( SERVICE_ACCOUNT_JSON, - new SettingsConfiguration.Builder(EnumSet.of(TaskType.TEXT_EMBEDDING, TaskType.RERANK, TaskType.CHAT_COMPLETION)) - .setDescription("API Key for the provider you're connecting to.") + new SettingsConfiguration.Builder( + EnumSet.of(TaskType.TEXT_EMBEDDING, TaskType.RERANK, TaskType.CHAT_COMPLETION, TaskType.COMPLETION) + ).setDescription("API Key for the provider you're connecting to.") .setLabel("Credentials JSON") .setRequired(true) .setSensitive(true) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiService.java index dc91e01322e6e..3b59e999125e5 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiService.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiService.java @@ -75,7 +75,8 @@ public class GoogleVertexAiService extends SenderService { private static final EnumSet supportedTaskTypes = EnumSet.of( TaskType.TEXT_EMBEDDING, TaskType.RERANK, - TaskType.CHAT_COMPLETION + TaskType.CHAT_COMPLETION, + TaskType.COMPLETION ); public static final EnumSet VALID_INPUT_TYPE_VALUES = EnumSet.of( @@ -87,13 +88,13 @@ public class GoogleVertexAiService extends SenderService { InputType.INTERNAL_SEARCH ); - private final ResponseHandler COMPLETION_HANDLER = new GoogleVertexAiUnifiedChatCompletionResponseHandler( + public static final ResponseHandler COMPLETION_HANDLER = new GoogleVertexAiUnifiedChatCompletionResponseHandler( "Google VertexAI chat completion" ); @Override public Set supportedStreamingTasks() { - return EnumSet.of(TaskType.CHAT_COMPLETION); + return EnumSet.of(TaskType.CHAT_COMPLETION, TaskType.COMPLETION); } public GoogleVertexAiService(HttpRequestSender.Factory factory, ServiceComponents serviceComponents) { @@ -358,7 +359,7 @@ private static GoogleVertexAiModel createModel( context ); - case CHAT_COMPLETION -> new GoogleVertexAiChatCompletionModel( + case CHAT_COMPLETION, COMPLETION -> new GoogleVertexAiChatCompletionModel( inferenceEntityId, taskType, NAME, @@ -396,10 +397,11 @@ public static InferenceServiceConfiguration get() { configurationMap.put( LOCATION, - new SettingsConfiguration.Builder(EnumSet.of(TaskType.TEXT_EMBEDDING, TaskType.CHAT_COMPLETION)).setDescription( - "Please provide the GCP region where the Vertex AI API(s) is enabled. " - + "For more information, refer to the {geminiVertexAIDocs}." - ) + new SettingsConfiguration.Builder(EnumSet.of(TaskType.TEXT_EMBEDDING, TaskType.CHAT_COMPLETION, TaskType.COMPLETION)) + .setDescription( + "Please provide the GCP region where the Vertex AI API(s) is enabled. " + + "For more information, refer to the {geminiVertexAIDocs}." + ) .setLabel("GCP Region") .setRequired(true) .setSensitive(false) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessor.java new file mode 100644 index 0000000000000..05fc9216c8916 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessor.java @@ -0,0 +1,64 @@ +/* + * 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.inference.services.googlevertexai; + +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.inference.results.StreamingChatCompletionResults; +import org.elasticsearch.xpack.inference.common.DelegatingProcessor; +import org.elasticsearch.xpack.inference.external.response.streaming.ServerSentEvent; + +import java.io.IOException; +import java.util.Deque; +import java.util.Objects; +import java.util.stream.Stream; + +public class GoogleVertexAiStreamingProcessor extends DelegatingProcessor, InferenceServiceResults.Result> { + + @Override + protected void next(Deque item) throws Exception { + var parserConfig = XContentParserConfiguration.EMPTY.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE); + var results = parseEvent(item, GoogleVertexAiStreamingProcessor::parse, parserConfig); + + if (results.isEmpty()) { + upstream().request(1); + } else { + downstream().onNext(new StreamingChatCompletionResults.Results(results)); + } + } + + public static Stream parse(XContentParserConfiguration parserConfig, ServerSentEvent event) { + String data = event.data(); + try (XContentParser jsonParser = XContentFactory.xContent(XContentType.JSON).createParser(parserConfig, data)) { + var chunk = GoogleVertexAiUnifiedStreamingProcessor.GoogleVertexAiChatCompletionChunkParser.parse(jsonParser); + + return chunk.choices() + .stream() + .map(choice -> choice.delta()) + .filter(Objects::nonNull) + .map(delta -> delta.content()) + .filter(content -> Strings.isNullOrEmpty(content) == false) + .map(StreamingChatCompletionResults.Result::new); + + } catch (IOException e) { + throw new ElasticsearchStatusException( + "Failed to parse event from inference provider: {}", + RestStatus.INTERNAL_SERVER_ERROR, + e, + event + ); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiUnifiedChatCompletionResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiUnifiedChatCompletionResponseHandler.java index 8c355c9f67f18..9e6fdb6eb8bb5 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiUnifiedChatCompletionResponseHandler.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiUnifiedChatCompletionResponseHandler.java @@ -23,10 +23,10 @@ import org.elasticsearch.xpack.core.inference.results.UnifiedChatCompletionException; import org.elasticsearch.xpack.inference.external.http.HttpResult; import org.elasticsearch.xpack.inference.external.http.retry.ErrorResponse; -import org.elasticsearch.xpack.inference.external.http.retry.ResponseParser; import org.elasticsearch.xpack.inference.external.request.Request; import org.elasticsearch.xpack.inference.external.response.streaming.ServerSentEventParser; import org.elasticsearch.xpack.inference.external.response.streaming.ServerSentEventProcessor; +import org.elasticsearch.xpack.inference.services.googlevertexai.response.GoogleVertexAiCompletionResponseEntity; import java.nio.charset.StandardCharsets; import java.util.Locale; @@ -43,10 +43,8 @@ public class GoogleVertexAiUnifiedChatCompletionResponseHandler extends GoogleVe private static final String ERROR_MESSAGE_FIELD = "message"; private static final String ERROR_STATUS_FIELD = "status"; - private static final ResponseParser noopParseFunction = (a, b) -> null; - public GoogleVertexAiUnifiedChatCompletionResponseHandler(String requestType) { - super(requestType, noopParseFunction, GoogleVertexAiErrorResponse::fromResponse, true); + super(requestType, GoogleVertexAiCompletionResponseEntity::fromResponse, GoogleVertexAiErrorResponse::fromResponse, true); } @Override @@ -64,6 +62,7 @@ public InferenceServiceResults parseResult(Request request, Flow.Publisher, Void> ERROR_PARSER = new ConstructingObjectParser<>( "google_vertex_ai_error_wrapper", @@ -138,7 +137,7 @@ private static class GoogleVertexAiErrorResponse extends ErrorResponse { ); } - static ErrorResponse fromResponse(HttpResult response) { + public static ErrorResponse fromResponse(HttpResult response) { try ( XContentParser parser = XContentFactory.xContent(XContentType.JSON) .createParser(XContentParserConfiguration.EMPTY, response.body()) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionCreator.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionCreator.java index 2aa42a8ae69c2..80d82df1cac26 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionCreator.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionCreator.java @@ -18,11 +18,13 @@ import org.elasticsearch.xpack.inference.services.ServiceComponents; import org.elasticsearch.xpack.inference.services.googlevertexai.GoogleVertexAiEmbeddingsRequestManager; import org.elasticsearch.xpack.inference.services.googlevertexai.GoogleVertexAiRerankRequestManager; +import org.elasticsearch.xpack.inference.services.googlevertexai.GoogleVertexAiResponseHandler; import org.elasticsearch.xpack.inference.services.googlevertexai.GoogleVertexAiUnifiedChatCompletionResponseHandler; import org.elasticsearch.xpack.inference.services.googlevertexai.completion.GoogleVertexAiChatCompletionModel; import org.elasticsearch.xpack.inference.services.googlevertexai.embeddings.GoogleVertexAiEmbeddingsModel; import org.elasticsearch.xpack.inference.services.googlevertexai.request.GoogleVertexAiUnifiedChatCompletionRequest; import org.elasticsearch.xpack.inference.services.googlevertexai.rerank.GoogleVertexAiRerankModel; +import org.elasticsearch.xpack.inference.services.googlevertexai.response.GoogleVertexAiCompletionResponseEntity; import java.util.Map; import java.util.Objects; @@ -36,9 +38,13 @@ public class GoogleVertexAiActionCreator implements GoogleVertexAiActionVisitor private final ServiceComponents serviceComponents; - static final ResponseHandler COMPLETION_HANDLER = new GoogleVertexAiUnifiedChatCompletionResponseHandler( - "Google VertexAI chat completion" + static final ResponseHandler CHAT_COMPLETION_HANDLER = new GoogleVertexAiResponseHandler( + "Google VertexAI completion", + GoogleVertexAiCompletionResponseEntity::fromResponse, + GoogleVertexAiUnifiedChatCompletionResponseHandler.GoogleVertexAiErrorResponse::fromResponse, + true ); + static final String USER_ROLE = "user"; public GoogleVertexAiActionCreator(Sender sender, ServiceComponents serviceComponents) { @@ -67,12 +73,12 @@ public ExecutableAction create(GoogleVertexAiRerankModel model, Map taskSettings) { - var failedToSendRequestErrorMessage = constructFailedToSendRequestMessage(COMPLETION_ERROR_PREFIX); + var manager = new GenericRequestManager<>( serviceComponents.threadPool(), model, - COMPLETION_HANDLER, + CHAT_COMPLETION_HANDLER, inputs -> new GoogleVertexAiUnifiedChatCompletionRequest(new UnifiedChatInput(inputs, USER_ROLE), model), ChatCompletionInput.class ); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionVisitor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionVisitor.java index eaa71f2646efe..fd3691d2981b1 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionVisitor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiActionVisitor.java @@ -21,4 +21,5 @@ public interface GoogleVertexAiActionVisitor { ExecutableAction create(GoogleVertexAiRerankModel model, Map taskSettings); ExecutableAction create(GoogleVertexAiChatCompletionModel model, Map taskSettings); + } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModel.java index 301d8f1075502..fdb4ed34d92db 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModel.java @@ -30,6 +30,9 @@ import static org.elasticsearch.core.Strings.format; public class GoogleVertexAiChatCompletionModel extends GoogleVertexAiModel { + + private final URI streamingURI; + public GoogleVertexAiChatCompletionModel( String inferenceEntityId, TaskType taskType, @@ -63,7 +66,8 @@ public GoogleVertexAiChatCompletionModel( serviceSettings ); try { - this.uri = buildUri(serviceSettings.location(), serviceSettings.projectId(), serviceSettings.modelId()); + this.streamingURI = buildUriStreaming(serviceSettings.location(), serviceSettings.projectId(), serviceSettings.modelId()); + this.nonStreamingUri = buildUriNonStreaming(serviceSettings.location(), serviceSettings.projectId(), serviceSettings.modelId()); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -114,7 +118,28 @@ public GoogleVertexAiSecretSettings getSecretSettings() { return (GoogleVertexAiSecretSettings) super.getSecretSettings(); } - public static URI buildUri(String location, String projectId, String model) throws URISyntaxException { + public URI streamingURI() { + return this.streamingURI; + } + + public static URI buildUriNonStreaming(String location, String projectId, String model) throws URISyntaxException { + return new URIBuilder().setScheme("https") + .setHost(format("%s%s", location, GoogleVertexAiUtils.GOOGLE_VERTEX_AI_HOST_SUFFIX)) + .setPathSegments( + GoogleVertexAiUtils.V1, + GoogleVertexAiUtils.PROJECTS, + projectId, + GoogleVertexAiUtils.LOCATIONS, + GoogleVertexAiUtils.GLOBAL, + GoogleVertexAiUtils.PUBLISHERS, + GoogleVertexAiUtils.PUBLISHER_GOOGLE, + GoogleVertexAiUtils.MODELS, + format("%s:%s", model, GoogleVertexAiUtils.GENERATE_CONTENT) + ) + .build(); + } + + public static URI buildUriStreaming(String location, String projectId, String model) throws URISyntaxException { return new URIBuilder().setScheme("https") .setHost(format("%s%s", location, GoogleVertexAiUtils.GOOGLE_VERTEX_AI_HOST_SUFFIX)) .setPathSegments( @@ -131,4 +156,25 @@ public static URI buildUri(String location, String projectId, String model) thro .setCustomQuery(GoogleVertexAiUtils.QUERY_PARAM_ALT_SSE) .build(); } + + @Override + public int rateLimitGroupingHash() { + // In VertexAI rate limiting is scoped to the project, region, model and endpoint. + // API Key does not affect the quota + // https://ai.google.dev/gemini-api/docs/rate-limits + // https://cloud.google.com/vertex-ai/docs/quotas + var projectId = getServiceSettings().projectId(); + var location = getServiceSettings().location(); + var modelId = getServiceSettings().modelId(); + + // Since we don't beforehand know which API is going to be used, we take a conservative approach and + // count both endpoint for the rate limit + return Objects.hash( + projectId, + location, + modelId, + GoogleVertexAiUtils.GENERATE_CONTENT, + GoogleVertexAiUtils.STREAM_GENERATE_CONTENT + ); + } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModel.java index 2bf9349db83fb..66031f7e5475d 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModel.java @@ -23,6 +23,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Map; +import java.util.Objects; import static org.elasticsearch.core.Strings.format; @@ -81,7 +82,7 @@ public GoogleVertexAiEmbeddingsModel(GoogleVertexAiEmbeddingsModel model, Google serviceSettings ); try { - this.uri = buildUri(serviceSettings.location(), serviceSettings.projectId(), serviceSettings.modelId()); + this.nonStreamingUri = buildUri(serviceSettings.location(), serviceSettings.projectId(), serviceSettings.modelId()); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -103,7 +104,7 @@ protected GoogleVertexAiEmbeddingsModel( serviceSettings ); try { - this.uri = new URI(uri); + this.nonStreamingUri = new URI(uri); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -150,4 +151,17 @@ public static URI buildUri(String location, String projectId, String modelId) th ) .build(); } + + @Override + public int rateLimitGroupingHash() { + // In VertexAI rate limiting is scoped to the project, region, model and endpoint. + // API Key does not affect the quota + // https://ai.google.dev/gemini-api/docs/rate-limits + // https://cloud.google.com/vertex-ai/docs/quotas + var projectId = getServiceSettings().projectId(); + var location = getServiceSettings().location(); + var modelId = getServiceSettings().modelId(); + + return Objects.hash(projectId, location, modelId, GoogleVertexAiUtils.PREDICT); + } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiEmbeddingsRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiEmbeddingsRequest.java index 53898a5f355d0..bf506a08d8268 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiEmbeddingsRequest.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiEmbeddingsRequest.java @@ -46,7 +46,7 @@ public GoogleVertexAiEmbeddingsRequest( @Override public HttpRequest createHttpRequest() { - HttpPost httpPost = new HttpPost(model.uri()); + HttpPost httpPost = new HttpPost(model.nonStreamingUri()); ByteArrayEntity byteEntity = new ByteArrayEntity( Strings.toString(new GoogleVertexAiEmbeddingsRequestEntity(truncationResult.input(), inputType, model.getTaskSettings())) @@ -84,7 +84,7 @@ public String getInferenceEntityId() { @Override public URI getURI() { - return model.uri(); + return model.nonStreamingUri(); } @Override diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiRerankRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiRerankRequest.java index 7939f3b70c21f..1fcdd5189b459 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiRerankRequest.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiRerankRequest.java @@ -50,7 +50,7 @@ public GoogleVertexAiRerankRequest( @Override public HttpRequest createHttpRequest() { - HttpPost httpPost = new HttpPost(model.uri()); + HttpPost httpPost = new HttpPost(model.nonStreamingUri()); ByteArrayEntity byteEntity = new ByteArrayEntity( Strings.toString( @@ -87,7 +87,7 @@ public String getInferenceEntityId() { @Override public URI getURI() { - return model.uri(); + return model.nonStreamingUri(); } @Override diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUnifiedChatCompletionRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUnifiedChatCompletionRequest.java index 7b20e71099e66..7acc859d26748 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUnifiedChatCompletionRequest.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUnifiedChatCompletionRequest.java @@ -25,15 +25,17 @@ public class GoogleVertexAiUnifiedChatCompletionRequest implements GoogleVertexA private final GoogleVertexAiChatCompletionModel model; private final UnifiedChatInput unifiedChatInput; + private final URI uri; public GoogleVertexAiUnifiedChatCompletionRequest(UnifiedChatInput unifiedChatInput, GoogleVertexAiChatCompletionModel model) { this.model = Objects.requireNonNull(model); this.unifiedChatInput = Objects.requireNonNull(unifiedChatInput); + this.uri = unifiedChatInput.stream() ? model.streamingURI() : model.nonStreamingUri(); } @Override public HttpRequest createHttpRequest() { - HttpPost httpPost = new HttpPost(model.uri()); + HttpPost httpPost = new HttpPost(uri); var requestEntity = new GoogleVertexAiUnifiedChatCompletionRequestEntity(unifiedChatInput); @@ -52,7 +54,7 @@ public void decorateWithAuth(HttpPost httpPost) { @Override public URI getURI() { - return model.uri(); + return this.uri; } @Override diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUtils.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUtils.java index 7eda9c8b01cae..633b787ff9cf3 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUtils.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/request/GoogleVertexAiUtils.java @@ -37,6 +37,8 @@ public final class GoogleVertexAiUtils { public static final String STREAM_GENERATE_CONTENT = "streamGenerateContent"; + public static final String GENERATE_CONTENT = "generateContent"; + public static final String QUERY_PARAM_ALT_SSE = "alt=sse"; private GoogleVertexAiUtils() {} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/rerank/GoogleVertexAiRerankModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/rerank/GoogleVertexAiRerankModel.java index 650402cd7f713..a77756a7c00b1 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/rerank/GoogleVertexAiRerankModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/rerank/GoogleVertexAiRerankModel.java @@ -22,10 +22,12 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Map; +import java.util.Objects; import static org.elasticsearch.core.Strings.format; public class GoogleVertexAiRerankModel extends GoogleVertexAiModel { + private static final String RERANK_RATE_LIMIT_ENDPOINT_ID = "rerank"; public GoogleVertexAiRerankModel( String inferenceEntityId, @@ -65,7 +67,7 @@ public GoogleVertexAiRerankModel(GoogleVertexAiRerankModel model, GoogleVertexAi serviceSettings ); try { - this.uri = buildUri(serviceSettings.projectId()); + this.nonStreamingUri = buildUri(serviceSettings.projectId()); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -87,7 +89,7 @@ protected GoogleVertexAiRerankModel( serviceSettings ); try { - this.uri = new URI(uri); + this.nonStreamingUri = new URI(uri); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -132,4 +134,16 @@ public static URI buildUri(String projectId) throws URISyntaxException { ) .build(); } + + @Override + public int rateLimitGroupingHash() { + // In VertexAI rate limiting is scoped to the project, region, model and endpoint. + // API Key does not affect the quota + // https://ai.google.dev/gemini-api/docs/rate-limits + // https://cloud.google.com/vertex-ai/docs/quotas + var projectId = getServiceSettings().projectId(); + var modelId = getServiceSettings().modelId(); + + return Objects.hash(projectId, modelId, RERANK_RATE_LIMIT_ENDPOINT_ID); + } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntity.java new file mode 100644 index 0000000000000..233981699f1da --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntity.java @@ -0,0 +1,103 @@ +/* + * 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.inference.services.googlevertexai.response; + +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.inference.results.ChatCompletionResults; +import org.elasticsearch.xpack.core.inference.results.StreamingUnifiedChatCompletionResults; +import org.elasticsearch.xpack.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.services.googlevertexai.GoogleVertexAiUnifiedStreamingProcessor; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.elasticsearch.xpack.inference.external.response.XContentUtils.moveToFirstToken; + +public class GoogleVertexAiCompletionResponseEntity { + /** + * Parses the response from Google Vertex AI's generateContent endpoint + * For a request like: + *

+     *     
+     *         {
+     *             "inputs": "Please summarize this text: some text"
+     *         }
+     *     
+     * 
+ * + * The response is a GenerateContentResponse objects that looks like: + * + *
+     *     
+     *
+     * {
+     *   "candidates": [
+     *     {
+     *       "content": {
+     *         "role": "model",
+     *         "parts": [
+     *           {
+     *             "text": "I am sorry, I cannot summarize the text because I do not have access to the text you are referring to."
+     *           }
+     *         ]
+     *       },
+     *       "finishReason": "STOP",
+     *       "avgLogprobs": -0.19326641248620074
+     *     }
+     *   ],
+     *   "usageMetadata": {
+     *     "promptTokenCount": 71,
+     *     "candidatesTokenCount": 23,
+     *     "totalTokenCount": 94,
+     *     "trafficType": "ON_DEMAND",
+     *     "promptTokensDetails": [
+     *       {
+     *         "modality": "TEXT",
+     *         "tokenCount": 71
+     *       }
+     *     ],
+     *     "candidatesTokensDetails": [
+     *       {
+     *         "modality": "TEXT",
+     *         "tokenCount": 23
+     *       }
+     *     ]
+     *   },
+     *   "modelVersion": "gemini-2.0-flash-001",
+     *   "createTime": "2025-05-28T15:08:20.049493Z",
+     *   "responseId": "5CY3aNWCA6mm4_UPr-zduAE"
+     * }
+     *    
+     * 
+ * + * @param request The original request made to the service. + **/ + public static InferenceServiceResults fromResponse(Request request, HttpResult response) throws IOException { + var responseJson = new String(response.body(), StandardCharsets.UTF_8); + + // Response from generateContent has the same shape as streamGenerateContent. We reuse the already implemented + // class to avoid code duplication + + StreamingUnifiedChatCompletionResults.ChatCompletionChunk chunk; + try ( + XContentParser parser = XContentFactory.xContent(XContentType.JSON) + .createParser(XContentParserConfiguration.EMPTY, responseJson) + ) { + moveToFirstToken(parser); + chunk = GoogleVertexAiUnifiedStreamingProcessor.GoogleVertexAiChatCompletionChunkParser.parse(parser); + } + var results = chunk.choices().stream().map(choice -> choice.delta().content()).map(ChatCompletionResults.Result::new).toList(); + + return new ChatCompletionResults(results); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiServiceTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiServiceTests.java index 3d7eb2d76ce47..99a09b983787d 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiServiceTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiServiceTests.java @@ -974,7 +974,7 @@ public void testGetConfiguration() throws Exception { { "service": "googlevertexai", "name": "Google Vertex AI", - "task_types": ["text_embedding", "rerank", "chat_completion"], + "task_types": ["text_embedding", "rerank", "completion", "chat_completion"], "configurations": { "service_account_json": { "description": "API Key for the provider you're connecting to.", @@ -983,7 +983,7 @@ public void testGetConfiguration() throws Exception { "sensitive": true, "updatable": true, "type": "str", - "supported_task_types": ["text_embedding", "rerank", "chat_completion"] + "supported_task_types": ["text_embedding", "rerank", "completion", "chat_completion"] }, "project_id": { "description": "The GCP Project ID which has Vertex AI API(s) enabled. For more information on the URL, refer to the {geminiVertexAIDocs}.", @@ -992,7 +992,7 @@ public void testGetConfiguration() throws Exception { "sensitive": false, "updatable": false, "type": "str", - "supported_task_types": ["text_embedding", "rerank", "chat_completion"] + "supported_task_types": ["text_embedding", "rerank", "completion", "chat_completion"] }, "location": { "description": "Please provide the GCP region where the Vertex AI API(s) is enabled. For more information, refer to the {geminiVertexAIDocs}.", @@ -1001,7 +1001,7 @@ public void testGetConfiguration() throws Exception { "sensitive": false, "updatable": false, "type": "str", - "supported_task_types": ["text_embedding", "chat_completion"] + "supported_task_types": ["text_embedding", "completion", "chat_completion"] }, "rate_limit.requests_per_minute": { "description": "Minimize the number of rate limit errors.", @@ -1010,7 +1010,7 @@ public void testGetConfiguration() throws Exception { "sensitive": false, "updatable": false, "type": "int", - "supported_task_types": ["text_embedding", "rerank", "chat_completion"] + "supported_task_types": ["text_embedding", "rerank", "completion", "chat_completion"] }, "model_id": { "description": "ID of the LLM you're using.", @@ -1019,7 +1019,7 @@ public void testGetConfiguration() throws Exception { "sensitive": false, "updatable": false, "type": "str", - "supported_task_types": ["text_embedding", "rerank", "chat_completion"] + "supported_task_types": ["text_embedding", "rerank", "completion", "chat_completion"] } } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessorTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessorTests.java new file mode 100644 index 0000000000000..2a915cd16dc7e --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/GoogleVertexAiStreamingProcessorTests.java @@ -0,0 +1,119 @@ +/* + * 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.inference.services.googlevertexai; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.ChunkedToXContent; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.core.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParseException; +import org.elasticsearch.xpack.inference.external.response.streaming.ServerSentEvent; + +import java.io.IOException; +import java.util.ArrayDeque; + +import static org.elasticsearch.xcontent.ToXContent.EMPTY_PARAMS; +import static org.elasticsearch.xpack.inference.common.DelegatingProcessorTests.onError; +import static org.elasticsearch.xpack.inference.common.DelegatingProcessorTests.onNext; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +public class GoogleVertexAiStreamingProcessorTests extends ESTestCase { + + public void testParseVertexAiResponse() throws IOException { + var item = new ArrayDeque(); + item.offer(new ServerSentEvent(vertexAiJsonResponse("test", true))); + + var response = onNext(new GoogleVertexAiStreamingProcessor(), item); + var json = toJsonString(response); + + assertThat(json, equalTo(""" + {"completion":[{"delta":"test"}]}""")); + } + + public void testParseVertexAiResponseMultiple() throws IOException { + var item = new ArrayDeque(); + item.offer(new ServerSentEvent(vertexAiJsonResponse("hello", false))); + + item.offer(new ServerSentEvent(vertexAiJsonResponse("world", true))); + + var response = onNext(new GoogleVertexAiStreamingProcessor(), item); + var json = toJsonString(response); + + assertThat(json, equalTo(""" + {"completion":[{"delta":"hello"},{"delta":"world"}]}""")); + } + + public void testParseErrorCallsOnError() { + var item = new ArrayDeque(); + item.offer(new ServerSentEvent("not json")); + + var exception = onError(new GoogleVertexAiStreamingProcessor(), item); + assertThat(exception, instanceOf(XContentParseException.class)); + } + + private String vertexAiJsonResponse(String content, boolean includeFinishReason) { + String finishReason = includeFinishReason ? "\"finishReason\": \"STOP\"," : ""; + + return Strings.format(""" + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "%s" + } + ] + }, + %s + "avgLogprobs": -0.19326641248620074 + } + ], + "usageMetadata": { + "promptTokenCount": 71, + "candidatesTokenCount": 23, + "totalTokenCount": 94, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 71 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 23 + } + ] + }, + "modelVersion": "gemini-2.0-flash-001", + "createTime": "2025-05-28T15:08:20.049493Z", + "responseId": "5CY3aNWCA6mm4_UPr-zduAE" + } + """, content, finishReason); + } + + private String toJsonString(ChunkedToXContent chunkedToXContent) throws IOException { + try (var builder = XContentFactory.jsonBuilder()) { + chunkedToXContent.toXContentChunked(EMPTY_PARAMS).forEachRemaining(xContent -> { + try { + xContent.toXContent(builder, EMPTY_PARAMS); + } catch (IOException e) { + logger.error(e.getMessage(), e); + fail(e.getMessage()); + } + }); + return XContentHelper.convertToJson(BytesReference.bytes(builder), false, builder.contentType()); + } + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiUnifiedChatCompletionActionTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiUnifiedChatCompletionActionTests.java index 58072b747a0aa..b7547857b8d2e 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiUnifiedChatCompletionActionTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/action/GoogleVertexAiUnifiedChatCompletionActionTests.java @@ -38,7 +38,7 @@ import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; import static org.elasticsearch.xpack.inference.external.action.ActionUtils.constructFailedToSendRequestMessage; -import static org.elasticsearch.xpack.inference.services.googlevertexai.action.GoogleVertexAiActionCreator.COMPLETION_HANDLER; +import static org.elasticsearch.xpack.inference.services.googlevertexai.GoogleVertexAiService.COMPLETION_HANDLER; import static org.elasticsearch.xpack.inference.services.googlevertexai.action.GoogleVertexAiActionCreator.USER_ROLE; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModelTests.java index fb5dccf89aa57..6a0ec6edfaa79 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModelTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/completion/GoogleVertexAiChatCompletionModelTests.java @@ -91,7 +91,7 @@ public void testBuildUri() throws URISyntaxException { "https://us-east1-aiplatform.googleapis.com/v1/projects/my-gcp-project" + "/locations/global/publishers/google/models/gemini-1.5-flash-001:streamGenerateContent?alt=sse" ); - URI actualUri = GoogleVertexAiChatCompletionModel.buildUri(location, projectId, model); + URI actualUri = GoogleVertexAiChatCompletionModel.buildUriStreaming(location, projectId, model); assertThat(actualUri, is(expectedUri)); } @@ -113,6 +113,6 @@ public static GoogleVertexAiChatCompletionModel createCompletionModel( } public static URI buildDefaultUri() throws URISyntaxException { - return GoogleVertexAiChatCompletionModel.buildUri(DEFAULT_LOCATION, DEFAULT_PROJECT_ID, DEFAULT_MODEL_ID); + return GoogleVertexAiChatCompletionModel.buildUriStreaming(DEFAULT_LOCATION, DEFAULT_PROJECT_ID, DEFAULT_MODEL_ID); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModelTests.java index 07169024e1e01..7eaa8e03fee66 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModelTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/embeddings/GoogleVertexAiEmbeddingsModelTests.java @@ -83,7 +83,7 @@ public void testOverrideWith_DoesNotOverrideModelUri() { var model = createModel("model", Boolean.FALSE, InputType.SEARCH); var overriddenModel = GoogleVertexAiEmbeddingsModel.of(model, Map.of()); - MatcherAssert.assertThat(overriddenModel.uri(), is(model.uri())); + MatcherAssert.assertThat(overriddenModel.nonStreamingUri(), is(model.nonStreamingUri())); } public static GoogleVertexAiEmbeddingsModel createModel( diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntityTests.java new file mode 100644 index 0000000000000..e634f75829743 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/googlevertexai/response/GoogleVertexAiCompletionResponseEntityTests.java @@ -0,0 +1,80 @@ +/* + * 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.inference.services.googlevertexai.response; + +import org.apache.http.HttpResponse; +import org.elasticsearch.core.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.inference.results.ChatCompletionResults; +import org.elasticsearch.xpack.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.request.Request; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class GoogleVertexAiCompletionResponseEntityTests extends ESTestCase { + + public void testFromResponse_Javadoc() throws IOException { + var responseText = "I am sorry, I cannot summarize the text because I do not have access to the text you are referring to."; + + String responseJson = Strings.format(""" + { + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "%s" + } + ] + }, + "finishReason": "STOP", + "avgLogprobs": -0.19326641248620074 + } + ], + "usageMetadata": { + "promptTokenCount": 71, + "candidatesTokenCount": 23, + "totalTokenCount": 94, + "trafficType": "ON_DEMAND", + "promptTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 71 + } + ], + "candidatesTokensDetails": [ + { + "modality": "TEXT", + "tokenCount": 23 + } + ] + }, + "modelVersion": "gemini-2.0-flash-001", + "createTime": "2025-05-28T15:08:20.049493Z", + "responseId": "5CY3aNWCA6mm4_UPr-zduAE" + } + """, responseText); + + var parsedResults = GoogleVertexAiCompletionResponseEntity.fromResponse( + mock(Request.class), + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assert parsedResults instanceof ChatCompletionResults; + var results = (ChatCompletionResults) parsedResults; + + assertThat(results.isStreaming(), is(false)); + assertThat(results.results().size(), is(1)); + assertThat(results.results().get(0).content(), is(responseText)); + } +} From 74b431d18014c88854b84d654997617f5a7326e4 Mon Sep 17 00:00:00 2001 From: Carlos Delgado <6339205+carlosdelest@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:11:42 +0200 Subject: [PATCH 027/102] ES|QL - kNN function initial support (#127322) --- .../esql/images/functions/knn.svg | 1 + .../esql/kibana/definition/functions/knn.json | 13 + .../esql/kibana/docs/functions/knn.md | 10 + .../vectors/DenseVectorFieldMapper.java | 5 + .../compute/lucene/LuceneQueryEvaluator.java | 2 +- .../xpack/esql/qa/rest/RestEsqlTestCase.java | 2 +- .../xpack/esql/CsvTestsDataLoader.java | 4 +- .../src/main/resources/data/colors.csv | 60 ++++ .../src/main/resources/knn-function.csv-spec | 285 +++++++++++++++++ .../src/main/resources/mapping-all-types.json | 3 + .../src/main/resources/mapping-colors.json | 20 ++ .../xpack/esql/DenseVectorFieldTypeIT.java | 63 +++- .../xpack/esql/plugin/KnnFunctionIT.java | 156 ++++++++++ .../xpack/esql/action/EsqlCapabilities.java | 7 +- .../xpack/esql/analysis/Analyzer.java | 23 ++ .../esql/expression/ExpressionWritables.java | 10 + .../function/EsqlFunctionRegistry.java | 4 +- .../function/fulltext/FullTextFunction.java | 4 +- .../esql/expression/function/vector/Knn.java | 292 ++++++++++++++++++ .../function/vector/VectorFunction.java | 15 + .../xpack/esql/querydsl/query/KnnQuery.java | 84 +++++ .../elasticsearch/xpack/esql/CsvTests.java | 4 + .../xpack/esql/SerializationTestUtils.java | 2 + .../xpack/esql/analysis/AnalyzerTests.java | 17 + .../xpack/esql/analysis/VerifierTests.java | 29 ++ .../function/fulltext/KnnTests.java | 132 ++++++++ .../function/fulltext/MatchTests.java | 2 +- .../LocalPhysicalPlanOptimizerTests.java | 25 ++ 28 files changed, 1251 insertions(+), 23 deletions(-) create mode 100644 docs/reference/query-languages/esql/images/functions/knn.svg create mode 100644 docs/reference/query-languages/esql/kibana/definition/functions/knn.json create mode 100644 docs/reference/query-languages/esql/kibana/docs/functions/knn.md create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/resources/data/colors.csv create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/resources/knn-function.csv-spec create mode 100644 x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-colors.json create mode 100644 x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/KnnFunctionIT.java create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/Knn.java create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/VectorFunction.java create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/KnnQuery.java create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java diff --git a/docs/reference/query-languages/esql/images/functions/knn.svg b/docs/reference/query-languages/esql/images/functions/knn.svg new file mode 100644 index 0000000000000..75a104a7cdcfa --- /dev/null +++ b/docs/reference/query-languages/esql/images/functions/knn.svg @@ -0,0 +1 @@ +KNN(field,query,options) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/knn.json b/docs/reference/query-languages/esql/kibana/definition/functions/knn.json new file mode 100644 index 0000000000000..48d3e582eec58 --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/definition/functions/knn.json @@ -0,0 +1,13 @@ +{ + "comment" : "This is generated by ESQL’s AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", + "type" : "scalar", + "name" : "knn", + "description" : "Finds the k nearest vectors to a query vector, as measured by a similarity metric. knn function finds nearest vectors through approximate search on indexed dense_vectors.", + "signatures" : [ ], + "examples" : [ + "from colors metadata _score\n| where knn(rgb_vector, [0, 120, 0])\n| sort _score desc", + "from colors metadata _score\n| where knn(rgb_vector, [0,255,255], {\"k\": 4})\n| sort _score desc" + ], + "preview" : true, + "snapshot_only" : true +} diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/knn.md b/docs/reference/query-languages/esql/kibana/docs/functions/knn.md new file mode 100644 index 0000000000000..45d1f294ea0a8 --- /dev/null +++ b/docs/reference/query-languages/esql/kibana/docs/functions/knn.md @@ -0,0 +1,10 @@ +% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +### KNN +Finds the k nearest vectors to a query vector, as measured by a similarity metric. knn function finds nearest vectors through approximate search on indexed dense_vectors. + +```esql +from colors metadata _score +| where knn(rgb_vector, [0, 120, 0]) +| sort _score desc +``` diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java index 40c69ec6e7fd4..9360d13c7833d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java @@ -2589,6 +2589,11 @@ public BlockLoader blockLoader(MappedFieldType.BlockLoaderContext blContext) { return null; } + if (dims == null) { + // No data has been indexed yet + return BlockLoader.CONSTANT_NULLS; + } + if (indexed) { return new BlockDocValuesReader.DenseVectorBlockLoader(name(), dims); } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java index 4644dd31f204f..d91df60621fce 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryEvaluator.java @@ -112,7 +112,7 @@ private Vector evalSingleSegmentNonDecreasing(DocVector docs) throws IOException int min = docs.docs().getInt(0); int max = docs.docs().getInt(docs.getPositionCount() - 1); int length = max - min + 1; - try (T scoreBuilder = createVectorBuilder(blockFactory, length)) { + try (T scoreBuilder = createVectorBuilder(blockFactory, docs.getPositionCount())) { if (length == docs.getPositionCount() && length > 1) { return segmentState.scoreDense(scoreBuilder, min, max); } diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java index ce13a655966cd..293200b227916 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java @@ -1022,7 +1022,7 @@ public void testMultipleBatchesWithLookupJoin() throws IOException { var query = requestObjectBuilder().query(format(null, "from * | lookup join {} on integer {}", testIndexName(), sort)); Map result = runEsql(query); var columns = as(result.get("columns"), List.class); - assertEquals(21, columns.size()); + assertEquals(22, columns.size()); var values = as(result.get("values"), List.class); assertEquals(10, values.size()); } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java index 2d06431806ab6..f1eb32afbdfee 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestsDataLoader.java @@ -148,6 +148,7 @@ public class CsvTestsDataLoader { private static final TestDataset LOGS = new TestDataset("logs"); private static final TestDataset MV_TEXT = new TestDataset("mv_text"); private static final TestDataset DENSE_VECTOR = new TestDataset("dense_vector"); + private static final TestDataset COLORS = new TestDataset("colors"); public static final Map CSV_DATASET_MAP = Map.ofEntries( Map.entry(EMPLOYEES.indexName, EMPLOYEES), @@ -210,7 +211,8 @@ public class CsvTestsDataLoader { Map.entry(SEMANTIC_TEXT.indexName, SEMANTIC_TEXT), Map.entry(LOGS.indexName, LOGS), Map.entry(MV_TEXT.indexName, MV_TEXT), - Map.entry(DENSE_VECTOR.indexName, DENSE_VECTOR) + Map.entry(DENSE_VECTOR.indexName, DENSE_VECTOR), + Map.entry(COLORS.indexName, COLORS) ); private static final EnrichConfig LANGUAGES_ENRICH = new EnrichConfig("languages_policy", "enrich-policy-languages.json"); diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/data/colors.csv b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/data/colors.csv new file mode 100644 index 0000000000000..b82ef7087a54c --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/data/colors.csv @@ -0,0 +1,60 @@ +color:text,hex_code:keyword,rgb_vector:dense_vector,primary:boolean +maroon, #800000, [128,0,0], false +brown, #A52A2A, [165,42,42], false +firebrick, #B22222, [178,34,34], false +crimson, #DC143C, [220,20,60], false +red, #FF0000, [255,0,0], true +tomato, #FF6347, [255,99,71], false +coral, #FF7F50, [255,127,80], false +salmon, #FA8072, [250,128,114], false +orange, #FFA500, [255,165,0], false +gold, #FFD700, [255,215,0], false +golden rod, #DAA520, [218,165,32], false +khaki, #F0E68C, [240,230,140], false +olive, #808000, [128,128,0], false +yellow, #FFFF00, [255,255,0], true +chartreuse, #7FFF00, [127,255,0], false +green, #008000, [0,128,0], true +lime, #00FF00, [0,255,0], false +teal, #008080, [0,128,128], false +cyan, #00FFFF, [0,255,255], true +turquoise, #40E0D0, [64,224,208], false +aqua marine, #7FFFD4, [127,255,212], false +navy, #000080, [0,0,128], false +blue, #0000FF, [0,0,255], true +indigo, #4B0082, [75,0,130], false +purple, #800080, [128,0,128], false +thistle, #D8BFD8, [216,191,216], false +plum, #DDA0DD, [221,160,221], false +violet, #EE82EE, [238,130,238], false +magenta, #FF00FF, [255,0,255], true +orchid, #DA70D6, [218,112,214], false +pink, #FFC0CB, [255,192,203], false +beige, #F5F5DC, [245,245,220], false +bisque, #FFE4C4, [255,228,196], false +wheat, #F5DEB3, [245,222,179], false +corn silk, #FFF8DC, [255,248,220], false +lemon chiffon, #FFFACD, [255,250,205], false +sienna, #A0522D, [160,82,45], false +chocolate, #D2691E, [210,105,30], false +peru, #CD853F, [205,133,63], false +burly wood, #DEB887, [222,184,135], false +tan, #D2B48C, [210,180,140], false +moccasin, #FFE4B5, [255,228,181], false +peach puff, #FFDAB9, [255,218,185], false +misty rose, #FFE4E1, [255,228,225], false +linen, #FAF0E6, [250,240,230], false +old lace, #FDF5E6, [253,245,230], false +papaya whip, #FFEFD5, [255,239,213], false +sea shell, #FFF5EE, [255,245,238], false +mint cream, #F5FFFA, [245,255,250], false +lavender, #E6E6FA, [230,230,250], false +honeydew, #F0FFF0, [240,255,240], false +ivory, #FFFFF0, [255,255,240], false +azure, #F0FFFF, [240,255,255], false +snow, #FFFAFA, [255,250,250], false +black, #000000, [0,0,0], true +gray, #808080, [128,128,128], true +silver, #C0C0C0, [192,192,192], false +gainsboro, #DCDCDC, [220,220,220], false +white, #FFFFFF, [255,255,255], true diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/knn-function.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/knn-function.csv-spec new file mode 100644 index 0000000000000..5e65e6269e652 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/knn-function.csv-spec @@ -0,0 +1,285 @@ +# TODO Most tests explicitly set k. Until knn function uses LIMIT as k, we need to explicitly set it to all values +# in the dataset to avoid test failures due to docs allocation in different shards, which can impact results for a +# top-n query at the shard level + +knnSearch +required_capability: knn_function + +// tag::knn-function[] +from colors metadata _score +| where knn(rgb_vector, [0, 120, 0]) +| sort _score desc, color asc +// end::knn-function[] +| keep color, rgb_vector +| limit 10 +; + +// tag::knn-function-result[] +color:text | rgb_vector:dense_vector +green | [0.0, 128.0, 0.0] +black | [0.0, 0.0, 0.0] +olive | [128.0, 128.0, 0.0] +teal | [0.0, 128.0, 128.0] +lime | [0.0, 255.0, 0.0] +sienna | [160.0, 82.0, 45.0] +maroon | [128.0, 0.0, 0.0] +navy | [0.0, 0.0, 128.0] +gray | [128.0, 128.0, 128.0] +chartreuse | [127.0, 255.0, 0.0] +// end::knn-function-result[] +; + +knnSearchWithKOption +required_capability: knn_function + +// tag::knn-function-options[] +from colors metadata _score +| where knn(rgb_vector, [0,255,255], {"k": 4}) +| sort _score desc, color asc +// end::knn-function-options[] +| keep color, rgb_vector +| limit 4 +; + +color:text | rgb_vector:dense_vector +cyan | [0.0, 255.0, 255.0] +turquoise | [64.0, 224.0, 208.0] +aqua marine | [127.0, 255.0, 212.0] +teal | [0.0, 128.0, 128.0] +; + +knnSearchWithSimilarityOption +required_capability: knn_function + +from colors metadata _score +| where knn(rgb_vector, [255,192,203], {"k": 140, "similarity": 40}) +| sort _score desc, color asc +| keep color, rgb_vector +; + +color:text | rgb_vector:dense_vector +pink | [255.0, 192.0, 203.0] +peach puff | [255.0, 218.0, 185.0] +bisque | [255.0, 228.0, 196.0] +wheat | [245.0, 222.0, 179.0] + +; + +knnHybridSearch +required_capability: knn_function + +from colors metadata _score +| where match(color, "blue") or knn(rgb_vector, [65,105,225], {"k": 140}) +| where primary == true +| sort _score desc, color asc +| keep color, rgb_vector +| limit 10 +; + +color:text | rgb_vector:dense_vector +blue | [0.0, 0.0, 255.0] +gray | [128.0, 128.0, 128.0] +cyan | [0.0, 255.0, 255.0] +magenta | [255.0, 0.0, 255.0] +green | [0.0, 128.0, 0.0] +white | [255.0, 255.0, 255.0] +black | [0.0, 0.0, 0.0] +red | [255.0, 0.0, 0.0] +yellow | [255.0, 255.0, 0.0] +; + +knnWithMultipleFunctions +required_capability: knn_function + +from colors metadata _score +| where knn(rgb_vector, [128,128,0], {"k": 140}) and match(color, "olive") +| sort _score desc, color asc +| keep color, rgb_vector +; + +color:text | rgb_vector:dense_vector +olive | [128.0, 128.0, 0.0] +; + +knnAfterKeep +required_capability: knn_function + +from colors metadata _score +| keep rgb_vector, color, _score +| where knn(rgb_vector, [128,255,0], {"k": 140}) +| sort _score desc, color asc +| keep rgb_vector +| limit 5 +; + +rgb_vector:dense_vector +[127.0, 255.0, 0.0] +[128.0, 128.0, 0.0] +[255.0, 255.0, 0.0] +[0.0, 255.0, 0.0] +[218.0, 165.0, 32.0] +; + +knnAfterDrop +required_capability: knn_function + +from colors metadata _score +| drop primary +| where knn(rgb_vector, [128,250,0], {"k": 140}) +| sort _score desc, color asc +| keep color, rgb_vector +| limit 5 +; + +color:text | rgb_vector: dense_vector +chartreuse | [127.0, 255.0, 0.0] +olive | [128.0, 128.0, 0.0] +yellow | [255.0, 255.0, 0.0] +golden rod | [218.0, 165.0, 32.0] +lime | [0.0, 255.0, 0.0] +; + +knnAfterEval +required_capability: knn_function + +from colors metadata _score +| eval composed_name = locate(color, " ") > 0 +| where knn(rgb_vector, [128,128,0], {"k": 140}) +| sort _score desc, color asc +| keep color, composed_name +| limit 5 +; + +color:text | composed_name:boolean +olive | false +sienna | false +chocolate | false +peru | false +golden rod | true +; + +knnWithConjunction +required_capability: knn_function + +# TODO We need kNN prefiltering here so we get more candidates that pass the filter +from colors metadata _score +| where knn(rgb_vector, [255,255,238], {"k": 140}) and hex_code like "#FFF*" +| sort _score desc, color asc +| keep color, hex_code, rgb_vector +| limit 10 +; + +color:text | hex_code:keyword | rgb_vector:dense_vector +ivory | #FFFFF0 | [255.0, 255.0, 240.0] +sea shell | #FFF5EE | [255.0, 245.0, 238.0] +snow | #FFFAFA | [255.0, 250.0, 250.0] +white | #FFFFFF | [255.0, 255.0, 255.0] +corn silk | #FFF8DC | [255.0, 248.0, 220.0] +lemon chiffon | #FFFACD | [255.0, 250.0, 205.0] +yellow | #FFFF00 | [255.0, 255.0, 0.0] +; + +knnWithDisjunctionAndFiltersConjunction +required_capability: knn_function + +# TODO We need kNN prefiltering here so we get more candidates that pass the filter +from colors metadata _score +| where (knn(rgb_vector, [0,255,255], {"k": 140}) or knn(rgb_vector, [128, 0, 255], {"k": 140})) and primary == true +| keep color, rgb_vector, _score +| sort _score desc, color asc +| drop _score +| limit 10 +; + +color:text | rgb_vector:dense_vector +cyan | [0.0, 255.0, 255.0] +blue | [0.0, 0.0, 255.0] +magenta | [255.0, 0.0, 255.0] +gray | [128.0, 128.0, 128.0] +white | [255.0, 255.0, 255.0] +green | [0.0, 128.0, 0.0] +black | [0.0, 0.0, 0.0] +red | [255.0, 0.0, 0.0] +yellow | [255.0, 255.0, 0.0] +; + +knnWithNonPushableConjunction +required_capability: knn_function + +from colors metadata _score +| eval composed_name = locate(color, " ") > 0 +| where knn(rgb_vector, [128,128,0], {"k": 140}) and composed_name == false +| sort _score desc, color asc +| keep color, composed_name +| limit 10 +; + +color:text | composed_name:boolean +olive | false +sienna | false +chocolate | false +peru | false +brown | false +firebrick | false +chartreuse | false +gray | false +green | false +maroon | false +; + +testKnnWithNonPushableDisjunctions +required_capability: knn_function + +from colors metadata _score +| where knn(rgb_vector, [128,128,0], {"k": 140, "similarity": 30}) or length(color) > 10 +| sort _score desc, color asc +| keep color +; + +color:text +olive +aqua marine +lemon chiffon +papaya whip +; + +testKnnWithNonPushableDisjunctionsOnComplexExpressions +required_capability: knn_function + +from colors metadata _score +| where (knn(rgb_vector, [128,128,0], {"k": 140, "similarity": 70}) and length(color) < 10) or (knn(rgb_vector, [128,0,128], {"k": 140, "similarity": 60}) and primary == false) +| sort _score desc, color asc +| keep color, primary +; + +color:text | primary:boolean +olive | false +purple | false +indigo | false +; + +testKnnInStatsNonPushable +required_capability: knn_function + +from colors +| where length(color) < 10 +| stats c = count(*) where knn(rgb_vector, [128,128,255], {"k": 140}) +; + +c: long +50 +; + +testKnnInStatsWithGrouping +required_capability: knn_function +required_capability: full_text_functions_in_stats_where + +from colors +| where length(color) < 10 +| stats c = count(*) where knn(rgb_vector, [128,128,255], {"k": 140}) by primary +; + +c: long | primary: boolean +41 | false +9 | true +; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-all-types.json b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-all-types.json index 17348adb6af4f..a7ef2f4840709 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-all-types.json +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-all-types.json @@ -63,6 +63,9 @@ "semantic_text": { "type": "semantic_text", "inference_id": "foo_inference_id" + }, + "dense_vector": { + "type": "dense_vector" } } } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-colors.json b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-colors.json new file mode 100644 index 0000000000000..24c4102e428f8 --- /dev/null +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/mapping-colors.json @@ -0,0 +1,20 @@ +{ + "properties": { + "color": { + "type": "text" + }, + "hex_code": { + "type": "keyword" + }, + "primary": { + "type": "boolean" + }, + "rgb_vector": { + "type": "dense_vector", + "similarity": "l2_norm", + "index_options": { + "type": "hnsw" + } + } + } +} diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/DenseVectorFieldTypeIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/DenseVectorFieldTypeIT.java index 905cf413fb48a..a130b026cd88a 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/DenseVectorFieldTypeIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/DenseVectorFieldTypeIT.java @@ -128,10 +128,57 @@ public void testRetrieveDenseVectorFieldData() { } } + public void testNonIndexedDenseVectorField() throws IOException { + createIndexWithDenseVector("no_dense_vectors"); + + int numDocs = randomIntBetween(10, 100); + IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs]; + for (int i = 0; i < numDocs; i++) { + docs[i] = prepareIndex("no_dense_vectors").setId("" + i).setSource("id", String.valueOf(i)); + } + + indexRandom(true, docs); + + var query = """ + FROM no_dense_vectors + | KEEP id, vector + """; + + try (var resp = run(query)) { + List> valuesList = EsqlTestUtils.getValuesList(resp); + assertEquals(numDocs, valuesList.size()); + valuesList.forEach(value -> { + assertEquals(2, value.size()); + Integer id = (Integer) value.get(0); + assertNotNull(id); + Object vector = value.get(1); + assertNull(vector); + }); + } + } + @Before public void setup() throws IOException { assumeTrue("Dense vector type is disabled", EsqlCapabilities.Cap.DENSE_VECTOR_FIELD_TYPE.isEnabled()); - var indexName = "test"; + + createIndexWithDenseVector("test"); + + int numDims = randomIntBetween(32, 64) * 2; // min 64, even number + int numDocs = randomIntBetween(10, 100); + IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs]; + for (int i = 0; i < numDocs; i++) { + List vector = new ArrayList<>(numDims); + for (int j = 0; j < numDims; j++) { + vector.add(randomFloat()); + } + docs[i] = prepareIndex("test").setId("" + i).setSource("id", String.valueOf(i), "vector", vector); + indexedVectors.put(i, vector); + } + + indexRandom(true, docs); + } + + private void createIndexWithDenseVector(String indexName) throws IOException { var client = client().admin().indices(); XContentBuilder mapping = XContentFactory.jsonBuilder() .startObject() @@ -161,19 +208,5 @@ public void setup() throws IOException { .setMapping(mapping) .setSettings(settingsBuilder.build()); assertAcked(CreateRequest); - - int numDims = randomIntBetween(32, 64) * 2; // min 64, even number - int numDocs = randomIntBetween(10, 100); - IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs]; - for (int i = 0; i < numDocs; i++) { - List vector = new ArrayList<>(numDims); - for (int j = 0; j < numDims; j++) { - vector.add(randomFloat()); - } - docs[i] = prepareIndex("test").setId("" + i).setSource("id", String.valueOf(i), "vector", vector); - indexedVectors.put(i, vector); - } - - indexRandom(true, docs); } } diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/KnnFunctionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/KnnFunctionIT.java new file mode 100644 index 0000000000000..a262943909938 --- /dev/null +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/KnnFunctionIT.java @@ -0,0 +1,156 @@ +/* + * 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.plugin; + +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xpack.esql.EsqlTestUtils; +import org.elasticsearch.xpack.esql.action.AbstractEsqlIntegTestCase; +import org.elasticsearch.xpack.esql.action.EsqlCapabilities; +import org.junit.Before; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; + +public class KnnFunctionIT extends AbstractEsqlIntegTestCase { + + private final Map> indexedVectors = new HashMap<>(); + private int numDocs; + private int numDims; + + public void testKnnDefaults() { + float[] queryVector = new float[numDims]; + Arrays.fill(queryVector, 1.0f); + + var query = String.format(Locale.ROOT, """ + FROM test METADATA _score + | WHERE knn(vector, %s) + | KEEP id, floats, _score, vector + | SORT _score DESC + """, Arrays.toString(queryVector)); + + try (var resp = run(query)) { + assertColumnNames(resp.columns(), List.of("id", "floats", "_score", "vector")); + assertColumnTypes(resp.columns(), List.of("integer", "double", "double", "dense_vector")); + + List> valuesList = EsqlTestUtils.getValuesList(resp); + assertEquals(Math.min(indexedVectors.size(), 10), valuesList.size()); + for (int i = 0; i < valuesList.size(); i++) { + List row = valuesList.get(i); + // Vectors should be in order of ID, as they're less similar than the query vector as the ID increases + assertEquals(i, row.getFirst()); + @SuppressWarnings("unchecked") + // Vectors should be the same + List floats = (List) row.get(1); + for (int j = 0; j < floats.size(); j++) { + assertEquals(floats.get(j).floatValue(), indexedVectors.get(i).get(j), 0f); + } + var score = (Double) row.get(2); + assertNotNull(score); + assertTrue(score > 0.0); + } + } + } + + public void testKnnOptions() { + float[] queryVector = new float[numDims]; + Arrays.fill(queryVector, 1.0f); + + var query = String.format(Locale.ROOT, """ + FROM test METADATA _score + | WHERE knn(vector, %s, {"k": 5}) + | KEEP id, floats, _score, vector + | SORT _score DESC + """, Arrays.toString(queryVector)); + + try (var resp = run(query)) { + assertColumnNames(resp.columns(), List.of("id", "floats", "_score", "vector")); + assertColumnTypes(resp.columns(), List.of("integer", "double", "double", "dense_vector")); + + List> valuesList = EsqlTestUtils.getValuesList(resp); + assertEquals(5, valuesList.size()); + } + } + + public void testKnnNonPushedDown() { + float[] queryVector = new float[numDims]; + Arrays.fill(queryVector, 1.0f); + + // TODO we need to decide what to do when / if user uses k for limit, as no more than k results will be returned from knn query + var query = String.format(Locale.ROOT, """ + FROM test METADATA _score + | WHERE knn(vector, %s, {"k": 5}) OR id > 10 + | KEEP id, floats, _score, vector + | SORT _score DESC + """, Arrays.toString(queryVector)); + + try (var resp = run(query)) { + assertColumnNames(resp.columns(), List.of("id", "floats", "_score", "vector")); + assertColumnTypes(resp.columns(), List.of("integer", "double", "double", "dense_vector")); + + List> valuesList = EsqlTestUtils.getValuesList(resp); + // K = 5, 1 more for every id > 10 + assertEquals(5 + Math.max(0, numDocs - 10 - 1), valuesList.size()); + } + } + + @Before + public void setup() throws IOException { + assumeTrue("Needs KNN support", EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()); + + var indexName = "test"; + var client = client().admin().indices(); + XContentBuilder mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + .startObject("id") + .field("type", "integer") + .endObject() + .startObject("vector") + .field("type", "dense_vector") + .field("similarity", "l2_norm") + .endObject() + .startObject("floats") + .field("type", "float") + .endObject() + .endObject() + .endObject(); + + Settings.Builder settingsBuilder = Settings.builder() + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1); + + var createRequest = client.prepareCreate(indexName).setMapping(mapping).setSettings(settingsBuilder.build()); + assertAcked(createRequest); + + numDocs = randomIntBetween(10, 20); + numDims = randomIntBetween(3, 10); + IndexRequestBuilder[] docs = new IndexRequestBuilder[numDocs]; + float value = 0.0f; + for (int i = 0; i < numDocs; i++) { + List vector = new ArrayList<>(numDims); + for (int j = 0; j < numDims; j++) { + vector.add(value++); + } + docs[i] = prepareIndex("test").setId("" + i).setSource("id", String.valueOf(i), "floats", vector, "vector", vector); + indexedVectors.put(i, vector); + } + + indexRandom(true, docs); + } +} 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 77fb1b077b3a6..d25f5b9e81d5b 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 @@ -1185,7 +1185,12 @@ public enum Cap { /** * MATCH PHRASE function */ - MATCH_PHRASE_FUNCTION; + MATCH_PHRASE_FUNCTION, + + /** + * Support knn function + */ + KNN_FUNCTION(Build.current().isSnapshot()); private final boolean enabled; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 259062cd14f57..494e801eb1f7a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -66,6 +66,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToLong; import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToUnsignedLong; import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce; +import org.elasticsearch.xpack.esql.expression.function.vector.VectorFunction; import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.DateTimeArithmeticOperation; import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.EsqlArithmeticOperation; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.In; @@ -1396,6 +1397,9 @@ private static Expression cast(org.elasticsearch.xpack.esql.core.expression.func if (f instanceof EsqlArithmeticOperation || f instanceof BinaryComparison) { return processBinaryOperator((BinaryOperator) f); } + if (f instanceof VectorFunction vectorFunction) { + return processVectorFunction(f); + } return f; } @@ -1595,6 +1599,25 @@ private static Expression castStringLiteral(Expression from, DataType target) { return unresolvedAttribute(from, target.toString(), e); } } + + private static Expression processVectorFunction(org.elasticsearch.xpack.esql.core.expression.function.Function vectorFunction) { + List args = vectorFunction.arguments(); + List newArgs = new ArrayList<>(); + for (Expression arg : args) { + if (arg.resolved() && arg.dataType().isNumeric() && arg.foldable()) { + Object folded = arg.fold(FoldContext.small() /* TODO remove me */); + if (folded instanceof List) { + Literal denseVector = new Literal(arg.source(), folded, DataType.DENSE_VECTOR); + newArgs.add(denseVector); + continue; + } + } + newArgs.add(arg); + } + + return vectorFunction.replaceChildren(newArgs); + } + } /** diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java index 2608ed96eb103..b0856a3090bae 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.esql.expression; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.core.expression.ExpressionCoreWritables; import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute; import org.elasticsearch.xpack.esql.expression.function.aggregate.AggregateWritables; @@ -83,6 +84,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.RLike; import org.elasticsearch.xpack.esql.expression.function.scalar.string.regex.WildcardLike; import org.elasticsearch.xpack.esql.expression.function.scalar.util.Delay; +import org.elasticsearch.xpack.esql.expression.function.vector.Knn; import org.elasticsearch.xpack.esql.expression.predicate.logical.Not; import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull; import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNull; @@ -115,6 +117,7 @@ public static List getNamedWriteables() { entries.addAll(binaryComparisons()); entries.addAll(fullText()); entries.addAll(unaryScalars()); + entries.addAll(vector()); return entries; } @@ -252,4 +255,11 @@ private static List binaryComparisons() { private static List fullText() { return FullTextWritables.getNamedWriteables(); } + + private static List vector() { + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + return List.of(Knn.ENTRY); + } + return List.of(); + } } 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 5d6bccc77ca51..ecc9dbd271327 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 @@ -179,6 +179,7 @@ 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.expression.function.scalar.util.Delay; +import org.elasticsearch.xpack.esql.expression.function.vector.Knn; import org.elasticsearch.xpack.esql.parser.ParsingException; import org.elasticsearch.xpack.esql.session.Configuration; @@ -485,7 +486,8 @@ private static FunctionDefinition[][] snapshotFunctions() { def(LastOverTime.class, LastOverTime::withUnresolvedTimestamp, "last_over_time"), def(FirstOverTime.class, FirstOverTime::withUnresolvedTimestamp, "first_over_time"), def(Term.class, bi(Term::new), "term"), - def(MatchPhrase.class, tri(MatchPhrase::new), "match_phrase") } }; + def(MatchPhrase.class, tri(MatchPhrase::new), "match_phrase"), + def(Knn.class, tri(Knn::new), "knn") } }; } public EsqlFunctionRegistry snapshotRegistry() { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java index bf7564ee40db4..b7bf29276511c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java @@ -148,7 +148,7 @@ public String functionType() { @Override public int hashCode() { - return Objects.hash(super.hashCode(), queryBuilder); + return Objects.hash(super.hashCode(), query, queryBuilder); } @Override @@ -157,7 +157,7 @@ public boolean equals(Object obj) { return false; } - return Objects.equals(queryBuilder, ((FullTextFunction) obj).queryBuilder); + return Objects.equals(queryBuilder, ((FullTextFunction) obj).queryBuilder) && Objects.equals(query, ((FullTextFunction) obj).query); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/Knn.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/Knn.java new file mode 100644 index 0000000000000..ecce0b069693d --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/Knn.java @@ -0,0 +1,292 @@ +/* + * 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.expression.function.vector; + +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.xpack.esql.core.InvalidArgumentException; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.FoldContext; +import org.elasticsearch.xpack.esql.core.expression.MapExpression; +import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; +import org.elasticsearch.xpack.esql.core.querydsl.query.Query; +import org.elasticsearch.xpack.esql.core.tree.NodeInfo; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.util.Check; +import org.elasticsearch.xpack.esql.expression.function.Example; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; +import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; +import org.elasticsearch.xpack.esql.expression.function.MapParam; +import org.elasticsearch.xpack.esql.expression.function.OptionalArgument; +import org.elasticsearch.xpack.esql.expression.function.Param; +import org.elasticsearch.xpack.esql.expression.function.fulltext.FullTextFunction; +import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; +import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; +import org.elasticsearch.xpack.esql.planner.TranslatorHandler; +import org.elasticsearch.xpack.esql.querydsl.query.KnnQuery; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static java.util.Map.entry; +import static org.elasticsearch.index.query.AbstractQueryBuilder.BOOST_FIELD; +import static org.elasticsearch.search.vectors.KnnVectorQueryBuilder.K_FIELD; +import static org.elasticsearch.search.vectors.KnnVectorQueryBuilder.NUM_CANDS_FIELD; +import static org.elasticsearch.search.vectors.KnnVectorQueryBuilder.VECTOR_SIMILARITY_FIELD; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.THIRD; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isMapExpression; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable; +import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; +import static org.elasticsearch.xpack.esql.core.type.DataType.DENSE_VECTOR; +import static org.elasticsearch.xpack.esql.core.type.DataType.FLOAT; +import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; +import static org.elasticsearch.xpack.esql.expression.function.fulltext.Match.getNameFromFieldAttribute; + +public class Knn extends FullTextFunction implements OptionalArgument, VectorFunction { + + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Knn", Knn::readFrom); + + private final Expression field; + private final Expression options; + + public static final Map ALLOWED_OPTIONS = Map.ofEntries( + entry(K_FIELD.getPreferredName(), INTEGER), + entry(NUM_CANDS_FIELD.getPreferredName(), INTEGER), + entry(VECTOR_SIMILARITY_FIELD.getPreferredName(), FLOAT), + entry(BOOST_FIELD.getPreferredName(), FLOAT), + entry(KnnQuery.RESCORE_OVERSAMPLE_FIELD, FLOAT) + ); + + @FunctionInfo( + returnType = "boolean", + preview = true, + description = "Finds the k nearest vectors to a query vector, as measured by a similarity metric. " + + "knn function finds nearest vectors through approximate search on indexed dense_vectors.", + examples = { + @Example(file = "knn-function", tag = "knn-function"), + @Example(file = "knn-function", tag = "knn-function-options"), }, + appliesTo = { @FunctionAppliesTo(lifeCycle = FunctionAppliesToLifecycle.DEVELOPMENT) } + ) + public Knn( + Source source, + @Param(name = "field", type = { "dense_vector" }, description = "Field that the query will target.") Expression field, + @Param( + name = "query", + type = { "dense_vector" }, + description = "Vector value to find top nearest neighbours for." + ) Expression query, + @MapParam( + name = "options", + params = { + @MapParam.MapParamEntry( + name = "boost", + type = "float", + valueHint = { "2.5" }, + description = "Floating point number used to decrease or increase the relevance scores of the query." + + "Defaults to 1.0." + ), + @MapParam.MapParamEntry( + name = "k", + type = "integer", + valueHint = { "10" }, + description = "The number of nearest neighbors to return from each shard. " + + "Elasticsearch collects k results from each shard, then merges them to find the global top results. " + + "This value must be less than or equal to num_candidates. Defaults to 10." + ), + @MapParam.MapParamEntry( + name = "num_candidates", + type = "integer", + valueHint = { "10" }, + description = "The number of nearest neighbor candidates to consider per shard while doing knn search. " + + "Cannot exceed 10,000. Increasing num_candidates tends to improve the accuracy of the final results. " + + "Defaults to 1.5 * k" + ), + @MapParam.MapParamEntry( + name = "similarity", + type = "double", + valueHint = { "0.01" }, + description = "The minimum similarity required for a document to be considered a match. " + + "The similarity value calculated relates to the raw similarity used, not the document score." + ), + @MapParam.MapParamEntry( + name = "rescore_oversample", + type = "double", + valueHint = { "3.5" }, + description = "Applies the specified oversampling for rescoring quantized vectors. " + + "See [oversampling and rescoring quantized vectors]" + + "(docs-content://solutions/search/vector/knn.md#dense-vector-knn-search-rescoring) for details." + ), }, + description = "(Optional) kNN additional options as <>." + + " See <> for more information.", + optional = true + ) Expression options + ) { + this(source, field, query, options, null); + } + + private Knn(Source source, Expression field, Expression query, Expression options, QueryBuilder queryBuilder) { + super(source, query, options == null ? List.of(field, query) : List.of(field, query, options), queryBuilder); + this.field = field; + this.options = options; + } + + public Expression field() { + return field; + } + + public Expression options() { + return options; + } + + @Override + public DataType dataType() { + return DataType.BOOLEAN; + } + + @Override + protected TypeResolution resolveParams() { + return resolveField().and(resolveQuery()).and(resolveOptions()); + } + + private TypeResolution resolveField() { + return isNotNull(field(), sourceText(), FIRST).and(isType(field(), dt -> dt == DENSE_VECTOR, sourceText(), FIRST, "dense_vector")); + } + + private TypeResolution resolveQuery() { + return isType(query(), dt -> dt == DENSE_VECTOR, sourceText(), TypeResolutions.ParamOrdinal.SECOND, "dense_vector").and( + isNotNullAndFoldable(query(), sourceText(), SECOND) + ); + } + + private TypeResolution resolveOptions() { + if (options() != null) { + TypeResolution resolution = isNotNull(options(), sourceText(), THIRD); + if (resolution.unresolved()) { + return resolution; + } + // MapExpression does not have a DataType associated with it + resolution = isMapExpression(options(), sourceText(), THIRD); + if (resolution.unresolved()) { + return resolution; + } + + try { + knnQueryOptions(); + } catch (InvalidArgumentException e) { + return new TypeResolution(e.getMessage()); + } + } + return TypeResolution.TYPE_RESOLVED; + } + + private Map knnQueryOptions() throws InvalidArgumentException { + if (options() == null) { + return Map.of(); + } + + Map matchOptions = new HashMap<>(); + populateOptionsMap((MapExpression) options(), matchOptions, THIRD, sourceText(), ALLOWED_OPTIONS); + return matchOptions; + } + + @Override + protected Query translate(TranslatorHandler handler) { + var fieldAttribute = Match.fieldAsFieldAttribute(field()); + + Check.notNull(fieldAttribute, "Match must have a field attribute as the first argument"); + String fieldName = getNameFromFieldAttribute(fieldAttribute); + @SuppressWarnings("unchecked") + List queryFolded = (List) query().fold(FoldContext.small() /* TODO remove me */); + float[] queryAsFloats = new float[queryFolded.size()]; + for (int i = 0; i < queryFolded.size(); i++) { + queryAsFloats[i] = queryFolded.get(i).floatValue(); + } + + return new KnnQuery(source(), fieldName, queryAsFloats, queryOptions()); + } + + @Override + public Expression replaceQueryBuilder(QueryBuilder queryBuilder) { + return new Knn(source(), field(), query(), options(), queryBuilder); + } + + private Map queryOptions() throws InvalidArgumentException { + if (options() == null) { + return Map.of(); + } + + Map options = new HashMap<>(); + populateOptionsMap((MapExpression) options(), options, THIRD, sourceText(), ALLOWED_OPTIONS); + return options; + } + + @Override + public Expression replaceChildren(List newChildren) { + return new Knn( + source(), + newChildren.get(0), + newChildren.get(1), + newChildren.size() > 2 ? newChildren.get(2) : null, + queryBuilder() + ); + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Knn::new, field(), query(), options()); + } + + @Override + public String getWriteableName() { + return ENTRY.name; + } + + private static Knn readFrom(StreamInput in) throws IOException { + Source source = Source.readFrom((PlanStreamInput) in); + Expression field = in.readNamedWriteable(Expression.class); + Expression query = in.readNamedWriteable(Expression.class); + QueryBuilder queryBuilder = in.readOptionalNamedWriteable(QueryBuilder.class); + + return new Knn(source, field, query, null, queryBuilder); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + source().writeTo(out); + out.writeNamedWriteable(field()); + out.writeNamedWriteable(query()); + out.writeOptionalNamedWriteable(queryBuilder()); + } + + @Override + public boolean equals(Object o) { + // Knn does not serialize options, as they get included in the query builder. We need to override equals and hashcode to + // ignore options when comparing two Knn functions + if (o == null || getClass() != o.getClass()) return false; + Knn knn = (Knn) o; + return Objects.equals(field(), knn.field()) + && Objects.equals(query(), knn.query()) + && Objects.equals(queryBuilder(), knn.queryBuilder()); + } + + @Override + public int hashCode() { + return Objects.hash(field(), query(), queryBuilder()); + } + +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/VectorFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/VectorFunction.java new file mode 100644 index 0000000000000..dc0be7a29fee0 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/vector/VectorFunction.java @@ -0,0 +1,15 @@ +/* + * 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.expression.function.vector; + +/** + * Marker interface for vector functions. Makes possible to do implicit casting + * from multi values to dense_vector field types, so parameters are actually + * processed as dense_vectors in vector functions + */ +public interface VectorFunction {} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/KnnQuery.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/KnnQuery.java new file mode 100644 index 0000000000000..aa0e896dfc013 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/querydsl/query/KnnQuery.java @@ -0,0 +1,84 @@ +/* + * 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.querydsl.query; + +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.vectors.KnnVectorQueryBuilder; +import org.elasticsearch.search.vectors.RescoreVectorBuilder; +import org.elasticsearch.xpack.esql.core.querydsl.query.Query; +import org.elasticsearch.xpack.esql.core.tree.Source; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.index.query.AbstractQueryBuilder.BOOST_FIELD; +import static org.elasticsearch.search.vectors.KnnVectorQueryBuilder.K_FIELD; +import static org.elasticsearch.search.vectors.KnnVectorQueryBuilder.NUM_CANDS_FIELD; +import static org.elasticsearch.search.vectors.KnnVectorQueryBuilder.VECTOR_SIMILARITY_FIELD; + +public class KnnQuery extends Query { + + private final String field; + private final float[] query; + private final Map options; + + public static final String RESCORE_OVERSAMPLE_FIELD = "rescore_oversample"; + + public KnnQuery(Source source, String field, float[] query, Map options) { + super(source); + assert options != null; + this.field = field; + this.query = query; + this.options = options; + } + + @Override + protected QueryBuilder asBuilder() { + Integer k = (Integer) options.get(K_FIELD.getPreferredName()); + Integer numCands = (Integer) options.get(NUM_CANDS_FIELD.getPreferredName()); + RescoreVectorBuilder rescoreVectorBuilder = null; + Float oversample = (Float) options.get(RESCORE_OVERSAMPLE_FIELD); + if (oversample != null) { + rescoreVectorBuilder = new RescoreVectorBuilder(oversample); + } + Float vectorSimilarity = (Float) options.get(VECTOR_SIMILARITY_FIELD.getPreferredName()); + + KnnVectorQueryBuilder queryBuilder = new KnnVectorQueryBuilder(field, query, k, numCands, rescoreVectorBuilder, vectorSimilarity); + Number boost = (Number) options.get(BOOST_FIELD.getPreferredName()); + if (boost != null) { + queryBuilder.boost(boost.floatValue()); + } + return queryBuilder; + } + + @Override + protected String innerToString() { + return "knn(" + field + ", " + Arrays.toString(query) + " options={" + options + "}))"; + } + + @Override + public boolean equals(Object o) { + if (super.equals(o) == false) return false; + + KnnQuery knnQuery = (KnnQuery) o; + return Objects.equals(field, knnQuery.field) + && Objects.deepEquals(query, knnQuery.query) + && Objects.equals(options, knnQuery.options); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), field, Arrays.hashCode(query), options); + } + + @Override + public boolean scorable() { + return true; + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java index 58860a163d945..0ab1b9c418963 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java @@ -296,6 +296,10 @@ public final void test() throws Throwable { "can't use KQL function in csv tests", testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.KQL_FUNCTION.capabilityName()) ); + assumeFalse( + "can't use KNN function in csv tests", + testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.KNN_FUNCTION.capabilityName()) + ); assumeFalse( "lookup join disabled for csv tests", testCase.requiredCapabilities.contains(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName()) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java index 8e396e4753f09..e55a1b039258e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java @@ -24,6 +24,7 @@ import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.index.query.WildcardQueryBuilder; +import org.elasticsearch.search.vectors.KnnVectorQueryBuilder; import org.elasticsearch.test.EqualsHashCodeTestUtils; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.expression.ExpressionWritables; @@ -111,6 +112,7 @@ public static NamedWriteableRegistry writableRegistry() { entries.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, WildcardQueryBuilder.NAME, WildcardQueryBuilder::new)); entries.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, RegexpQueryBuilder.NAME, RegexpQueryBuilder::new)); entries.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, ExistsQueryBuilder.NAME, ExistsQueryBuilder::new)); + entries.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, KnnVectorQueryBuilder.NAME, KnnVectorQueryBuilder::new)); entries.add(SingleValueQuery.ENTRY); entries.addAll(ExpressionWritables.getNamedWriteables()); entries.addAll(PlanWritables.getNamedWriteables()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index d53a392650f83..19fb563488e61 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -55,6 +55,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToLong; import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat; import org.elasticsearch.xpack.esql.expression.function.scalar.string.Substring; +import org.elasticsearch.xpack.esql.expression.function.vector.Knn; import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan; @@ -2363,6 +2364,22 @@ public void testImplicitCasting() { assertThat(e.getMessage(), containsString("[+] has arguments with incompatible types [datetime] and [datetime]")); } + public void testDenseVectorImplicitCasting() { + Analyzer analyzer = analyzer(loadMapping("mapping-dense_vector.json", "vectors")); + + var plan = analyze(""" + from test | where knn(vector, [0.342, 0.164, 0.234]) + """, "mapping-dense_vector.json"); + + var limit = as(plan, Limit.class); + var filter = as(limit.child(), Filter.class); + var knn = as(filter.condition(), Knn.class); + var field = knn.field(); + var queryVector = as(knn.query(), Literal.class); + assertEquals(DataType.DENSE_VECTOR, queryVector.dataType()); + assertThat(queryVector.value(), equalTo(List.of(0.342, 0.164, 0.234))); + } + public void testRateRequiresCounterTypes() { assumeTrue("rate requires snapshot builds", Build.current().isSnapshot()); Analyzer analyzer = analyzer(tsdbIndexResolution()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index b32a74cd66f41..14e5c0615e2b4 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -22,6 +22,7 @@ import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchPhrase; import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch; import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString; +import org.elasticsearch.xpack.esql.expression.function.vector.Knn; import org.elasticsearch.xpack.esql.index.EsIndex; import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.parser.EsqlParser; @@ -1234,6 +1235,9 @@ public void testFieldBasedFullTextFunctions() throws Exception { checkFieldBasedWithNonIndexedColumn("Term", "term(text, \"cat\")", "function"); checkFieldBasedFunctionNotAllowedAfterCommands("Term", "function", "term(title, \"Meditation\")"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkFieldBasedFunctionNotAllowedAfterCommands("KNN", "function", "knn(vector, [1, 2, 3])"); + } } private void checkFieldBasedFunctionNotAllowedAfterCommands(String functionName, String functionType, String functionInvocation) { @@ -1364,6 +1368,9 @@ public void testFullTextFunctionsOnlyAllowedInWhere() throws Exception { if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { checkFullTextFunctionsOnlyAllowedInWhere("MultiMatch", "multi_match(\"Meditation\", title, body)", "function"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkFullTextFunctionsOnlyAllowedInWhere("KNN", "knn(vector, [0, 1, 2])", "function"); + } } private void checkFullTextFunctionsOnlyAllowedInWhere(String functionName, String functionInvocation, String functionType) @@ -1400,6 +1407,9 @@ public void testFullTextFunctionsDisjunctions() { if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { checkWithFullTextFunctionsDisjunctions("term(title, \"Meditation\")"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkWithFullTextFunctionsDisjunctions("knn(vector, [1, 2, 3])"); + } } private void checkWithFullTextFunctionsDisjunctions(String functionInvocation) { @@ -1462,6 +1472,9 @@ public void testFullTextFunctionsWithNonBooleanFunctions() { if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { checkFullTextFunctionsWithNonBooleanFunctions("Term", "term(title, \"Meditation\")", "function"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkFullTextFunctionsWithNonBooleanFunctions("KNN", "knn(vector, [1, 2, 3])", "function"); + } } private void checkFullTextFunctionsWithNonBooleanFunctions(String functionName, String functionInvocation, String functionType) { @@ -1530,6 +1543,9 @@ public void testFullTextFunctionsTargetsExistingField() throws Exception { if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { testFullTextFunctionTargetsExistingField("term(fist_name, \"Meditation\")"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + testFullTextFunctionTargetsExistingField("knn(vector, [0, 1, 2])"); + } } private void testFullTextFunctionTargetsExistingField(String functionInvocation) throws Exception { @@ -2055,6 +2071,9 @@ public void testFullTextFunctionOptions() { if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { checkOptionDataTypes(MultiMatch.OPTIONS, "FROM test | WHERE MULTI_MATCH(\"Jean\", title, body, {\"%s\": %s})"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkOptionDataTypes(Knn.ALLOWED_OPTIONS, "FROM test | WHERE KNN(vector, [0.1, 0.2, 0.3], {\"%s\": %s})"); + } } /** @@ -2140,6 +2159,10 @@ public void testFullTextFunctionsNullArgs() throws Exception { checkFullTextFunctionNullArgs("term(null, \"query\")", "first"); checkFullTextFunctionNullArgs("term(title, null)", "second"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkFullTextFunctionNullArgs("knn(null, [0, 1, 2])", "first"); + checkFullTextFunctionNullArgs("knn(vector, null)", "second"); + } } private void checkFullTextFunctionNullArgs(String functionInvocation, String argOrdinal) throws Exception { @@ -2161,6 +2184,9 @@ public void testFullTextFunctionsConstantQuery() throws Exception { if (EsqlCapabilities.Cap.TERM_FUNCTION.isEnabled()) { checkFullTextFunctionsConstantQuery("term(title, tags)", "second"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkFullTextFunctionsConstantQuery("knn(vector, vector)", "second"); + } } private void checkFullTextFunctionsConstantQuery(String functionInvocation, String argOrdinal) throws Exception { @@ -2188,6 +2214,9 @@ public void testFullTextFunctionsInStats() { if (EsqlCapabilities.Cap.MULTI_MATCH_FUNCTION.isEnabled()) { checkFullTextFunctionsInStats("multi_match(\"Meditation\", title, body)"); } + if (EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()) { + checkFullTextFunctionsInStats("knn(vector, [0, 1, 2])"); + } } private void checkFullTextFunctionsInStats(String functionInvocation) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java new file mode 100644 index 0000000000000..c2bc381e2663c --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/KnnTests.java @@ -0,0 +1,132 @@ +/* + * 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.expression.function.fulltext; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.xpack.esql.action.EsqlCapabilities; +import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; +import org.elasticsearch.xpack.esql.core.expression.Literal; +import org.elasticsearch.xpack.esql.core.expression.MapExpression; +import org.elasticsearch.xpack.esql.core.tree.Source; +import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.elasticsearch.xpack.esql.expression.function.vector.Knn; +import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput; +import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates; +import org.junit.Before; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import static org.elasticsearch.xpack.esql.SerializationTestUtils.serializeDeserialize; +import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN; +import static org.elasticsearch.xpack.esql.core.type.DataType.DENSE_VECTOR; +import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; +import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; +import static org.elasticsearch.xpack.esql.planner.TranslatorHandler.TRANSLATOR_HANDLER; +import static org.hamcrest.Matchers.equalTo; + +public class KnnTests extends AbstractFunctionTestCase { + + public KnnTests(@Name("TestCase") Supplier testCaseSupplier) { + this.testCase = testCaseSupplier.get(); + } + + @ParametersFactory + public static Iterable parameters() { + return parameterSuppliersFromTypedData(addFunctionNamedParams(testCaseSuppliers())); + } + + @Before + public void checkCapability() { + assumeTrue("KNN is not enabled", EsqlCapabilities.Cap.KNN_FUNCTION.isEnabled()); + } + + private static List testCaseSuppliers() { + List suppliers = new ArrayList<>(); + + suppliers.add( + TestCaseSupplier.testCaseSupplier( + new TestCaseSupplier.TypedDataSupplier("dense_vector field", KnnTests::randomDenseVector, DENSE_VECTOR), + new TestCaseSupplier.TypedDataSupplier("query", KnnTests::randomDenseVector, DENSE_VECTOR, true), + (d1, d2) -> equalTo("string"), + BOOLEAN, + (o1, o2) -> true + ) + ); + + return suppliers; + } + + private static List randomDenseVector() { + int dimensions = randomIntBetween(64, 128); + List vector = new ArrayList<>(); + for (int i = 0; i < dimensions; i++) { + vector.add(randomFloat()); + } + return vector; + } + + /** + * Adds function named parameters to all the test case suppliers provided + */ + private static List addFunctionNamedParams(List suppliers) { + // TODO get to a common class with MatchTests + List result = new ArrayList<>(); + for (TestCaseSupplier supplier : suppliers) { + List dataTypes = new ArrayList<>(supplier.types()); + dataTypes.add(UNSUPPORTED); + result.add(new TestCaseSupplier(supplier.name() + ", options", dataTypes, () -> { + List values = new ArrayList<>(supplier.get().getData()); + values.add( + new TestCaseSupplier.TypedData( + new MapExpression(Source.EMPTY, List.of(new Literal(Source.EMPTY, randomAlphaOfLength(10), KEYWORD))), + UNSUPPORTED, + "options" + ).forceLiteral() + ); + + return new TestCaseSupplier.TestCase(values, equalTo("KnnEvaluator"), BOOLEAN, equalTo(true)); + })); + } + return result; + } + + @Override + protected Expression build(Source source, List args) { + Knn knn = new Knn(source, args.get(0), args.get(1), args.size() > 2 ? args.get(2) : null); + // We need to add the QueryBuilder to the match expression, as it is used to implement equals() and hashCode() and + // thus test the serialization methods. But we can only do this if the parameters make sense . + if (args.get(0) instanceof FieldAttribute && args.get(1).foldable()) { + QueryBuilder queryBuilder = TRANSLATOR_HANDLER.asQuery(LucenePushdownPredicates.DEFAULT, knn).toQueryBuilder(); + knn = (Knn) knn.replaceQueryBuilder(queryBuilder); + } + return knn; + } + + /** + * Copy of the overridden method that doesn't check for children size, as the {@code options} child isn't serialized in Match. + */ + @Override + protected Expression serializeDeserializeExpression(Expression expression) { + Expression newExpression = serializeDeserialize( + expression, + PlanStreamOutput::writeNamedWriteable, + in -> in.readNamedWriteable(Expression.class), + testCase.getConfiguration() // The configuration query should be == to the source text of the function for this to work + ); + // Fields use synthetic sources, which can't be serialized. So we use the originals instead. + return newExpression.replaceChildren(expression.children()); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java index 6993f7583dd02..301cbd6844afe 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/MatchTests.java @@ -82,7 +82,7 @@ protected Expression build(Source source, List args) { // thus test the serialization methods. But we can only do this if the parameters make sense . if (args.get(0) instanceof FieldAttribute && args.get(1).foldable()) { QueryBuilder queryBuilder = TRANSLATOR_HANDLER.asQuery(LucenePushdownPredicates.DEFAULT, match).toQueryBuilder(); - match.replaceQueryBuilder(queryBuilder); + match = (Match) match.replaceQueryBuilder(queryBuilder); } return match; } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java index 08c9a2f8b4fee..b0f16b384a489 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java @@ -28,6 +28,8 @@ import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.search.vectors.KnnVectorQueryBuilder; +import org.elasticsearch.search.vectors.RescoreVectorBuilder; import org.elasticsearch.test.VersionUtils; import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.esql.EsqlTestUtils; @@ -1359,6 +1361,29 @@ public void testMultiMatchOptionsPushDown() { assertThat(expectedQuery.toString(), is(planStr.get())); } + public void testKnnOptionsPushDown() { + String query = """ + from test + | where KNN(dense_vector, [0.1, 0.2, 0.3], + { "k": 5, "similarity": 0.001, "num_candidates": 10, "rescore_oversample": 7, "boost": 3.5 }) + """; + var analyzer = makeAnalyzer("mapping-all-types.json"); + var plan = plannerOptimizer.plan(query, IS_SV_STATS, analyzer); + + AtomicReference planStr = new AtomicReference<>(); + plan.forEachDown(EsQueryExec.class, result -> planStr.set(result.query().toString())); + + var expectedQuery = new KnnVectorQueryBuilder( + "dense_vector", + new float[] { 0.1f, 0.2f, 0.3f }, + 5, + 10, + new RescoreVectorBuilder(7), + 0.001f + ).boost(3.5f); + assertThat(expectedQuery.toString(), is(planStr.get())); + } + /** * Expecting * LimitExec[1000[INTEGER]] From c678ebd1a0942bcca25c5ea4bbf47621dc6943d3 Mon Sep 17 00:00:00 2001 From: Jan Kuipers <148754765+jan-elastic@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:19:44 +0200 Subject: [PATCH 028/102] Remove optional seed from ES|QL SAMPLE (#128887) * Remove optional seed from ES|QL SAMPLE * make it clear that seed is for testing --- .../compute/operator/SampleOperator.java | 20 +- .../compute/operator/SampleOperatorTests.java | 3 +- .../esql/src/main/antlr/EsqlBaseParser.g4 | 2 +- .../logical/PushDownAndCombineSample.java | 21 +- .../physical/local/PushSampleToSource.java | 3 - .../xpack/esql/parser/EsqlBaseParser.interp | 2 +- .../xpack/esql/parser/EsqlBaseParser.java | 1081 ++++++++--------- .../xpack/esql/parser/LogicalPlanBuilder.java | 16 +- .../xpack/esql/plan/logical/Sample.java | 21 +- .../xpack/esql/plan/physical/SampleExec.java | 38 +- .../esql/planner/LocalExecutionPlanner.java | 4 +- .../esql/planner/mapper/LocalMapper.java | 2 +- .../xpack/esql/planner/mapper/Mapper.java | 2 +- .../optimizer/LogicalPlanOptimizerTests.java | 10 +- .../optimizer/PhysicalPlanOptimizerTests.java | 17 +- .../esql/parser/StatementParserTests.java | 3 +- .../plan/logical/CommandLicenseTests.java | 2 +- .../logical/SampleSerializationTests.java | 10 +- .../SampleExecSerializationTests.java | 11 +- 19 files changed, 585 insertions(+), 683 deletions(-) diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/SampleOperator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/SampleOperator.java index 0a2158015c950..56ba95f66f5fa 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/SampleOperator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/SampleOperator.java @@ -9,6 +9,7 @@ import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersions; +import org.elasticsearch.common.Randomness; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; @@ -27,16 +28,29 @@ public class SampleOperator implements Operator { - public record Factory(double probability, int seed) implements OperatorFactory { + public static class Factory implements OperatorFactory { + + private final double probability; + private final Integer seed; + + public Factory(double probability) { + this(probability, null); + } + + // visible for testing + Factory(double probability, Integer seed) { + this.probability = probability; + this.seed = seed; + } @Override public SampleOperator get(DriverContext driverContext) { - return new SampleOperator(probability, seed); + return new SampleOperator(probability, seed == null ? Randomness.get().nextInt() : seed); } @Override public String describe() { - return "SampleOperator[probability = " + probability + ", seed = " + seed + "]"; + return "SampleOperator[probability = " + probability + "]"; } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SampleOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SampleOperatorTests.java index c66c48d2af783..87a7e24a2c3d4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SampleOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SampleOperatorTests.java @@ -21,7 +21,6 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.matchesPattern; public class SampleOperatorTests extends OperatorTestCase { @@ -46,7 +45,7 @@ protected SampleOperator.Factory simple(SimpleOptions options) { @Override protected Matcher expectedDescriptionOfSimple() { - return matchesPattern("SampleOperator\\[probability = 0.5, seed = -?\\d+]"); + return equalTo("SampleOperator[probability = 0.5]"); } @Override diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 index 3d48c50e95da0..974533fcff9bc 100644 --- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 +++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 @@ -312,5 +312,5 @@ completionCommand ; sampleCommand - : DEV_SAMPLE probability=decimalValue seed=integerValue? + : DEV_SAMPLE probability=decimalValue ; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineSample.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineSample.java index 5c786d2fc2c24..3c2f2a17650e5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineSample.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineSample.java @@ -62,8 +62,7 @@ protected LogicalPlan rule(Sample sample, LogicalOptimizerContext context) { var child = sample.child(); if (child instanceof Sample sampleChild) { var probability = combinedProbability(context, sample, sampleChild); - var seed = combinedSeed(context, sample, sampleChild); - plan = new Sample(sample.source(), probability, seed, sampleChild.child()); + plan = new Sample(sample.source(), probability, sampleChild.child()); } else if (child instanceof Enrich || child instanceof Eval || child instanceof Filter @@ -82,22 +81,4 @@ private static Expression combinedProbability(LogicalOptimizerContext context, S var childProbability = (double) Foldables.valueOf(context.foldCtx(), child.probability()); return Literal.of(parent.probability(), parentProbability * childProbability); } - - private static Expression combinedSeed(LogicalOptimizerContext context, Sample parent, Sample child) { - var parentSeed = parent.seed(); - var childSeed = child.seed(); - Expression seed; - if (parentSeed != null) { - if (childSeed != null) { - var seedValue = (int) Foldables.valueOf(context.foldCtx(), parentSeed); - seedValue ^= (int) Foldables.valueOf(context.foldCtx(), childSeed); - seed = Literal.of(parentSeed, seedValue); - } else { - seed = parentSeed; - } - } else { - seed = childSeed; - } - return seed; - } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushSampleToSource.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushSampleToSource.java index ef663d06999c2..4e41b748ece08 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushSampleToSource.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushSampleToSource.java @@ -33,9 +33,6 @@ protected PhysicalPlan rule(SampleExec sample, LocalPhysicalOptimizerContext ctx } var sampleQuery = new RandomSamplingQueryBuilder((double) Foldables.valueOf(ctx.foldCtx(), sample.probability())); - if (sample.seed() != null) { - sampleQuery.seed((int) Foldables.valueOf(ctx.foldCtx(), sample.seed())); - } fullQuery.filter(sampleQuery); 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 3cc1109d65077..30994e3f1ba88 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 @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 3, 60, 544, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 804, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 543, 3, 148, 74, 0, 542, 544, 3, 150, 75, 0, 543, 542, 1, 0, 0, 0, 543, 544, 1, 0, 0, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 72, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 72, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 68, 0, 0, 555, 556, 5, 100, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 63, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 101, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 69, 0, 0, 568, 570, 5, 72, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 73, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 76, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 72, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 71, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 72, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 78, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 61, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 62, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 100, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 101, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 61, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 100, 0, 0, 655, 669, 5, 90, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 63, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 63, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 101, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 93, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 63, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 94, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 62, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 73, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 102, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 98, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 63, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 99, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 98, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 63, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 99, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 98, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 63, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 99, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 115, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 75, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 63, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 71, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 543, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ No newline at end of file +[4, 1, 139, 770, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 551, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 558, 8, 61, 10, 61, 12, 61, 561, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 568, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 573, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 581, 8, 61, 10, 61, 12, 61, 584, 9, 61, 1, 62, 1, 62, 3, 62, 588, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 595, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 600, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 605, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 615, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 621, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 629, 8, 65, 10, 65, 12, 65, 632, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 642, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 647, 8, 66, 10, 66, 12, 66, 650, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 658, 8, 67, 10, 67, 12, 67, 661, 9, 67, 1, 67, 1, 67, 3, 67, 665, 8, 67, 3, 67, 667, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 677, 8, 69, 10, 69, 12, 69, 680, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 701, 8, 71, 10, 71, 12, 71, 704, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 712, 8, 71, 10, 71, 12, 71, 715, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 723, 8, 71, 10, 71, 12, 71, 726, 9, 71, 1, 71, 1, 71, 3, 71, 730, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 736, 8, 73, 1, 74, 3, 74, 739, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 744, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 763, 8, 80, 10, 80, 12, 80, 766, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 801, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 572, 1, 0, 0, 0, 124, 599, 1, 0, 0, 0, 126, 601, 1, 0, 0, 0, 128, 614, 1, 0, 0, 0, 130, 620, 1, 0, 0, 0, 132, 641, 1, 0, 0, 0, 134, 651, 1, 0, 0, 0, 136, 670, 1, 0, 0, 0, 138, 672, 1, 0, 0, 0, 140, 683, 1, 0, 0, 0, 142, 729, 1, 0, 0, 0, 144, 731, 1, 0, 0, 0, 146, 735, 1, 0, 0, 0, 148, 738, 1, 0, 0, 0, 150, 743, 1, 0, 0, 0, 152, 747, 1, 0, 0, 0, 154, 749, 1, 0, 0, 0, 156, 751, 1, 0, 0, 0, 158, 756, 1, 0, 0, 0, 160, 758, 1, 0, 0, 0, 162, 767, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 542, 3, 148, 74, 0, 542, 121, 1, 0, 0, 0, 543, 544, 6, 61, -1, 0, 544, 545, 5, 72, 0, 0, 545, 573, 3, 122, 61, 8, 546, 573, 3, 128, 64, 0, 547, 573, 3, 124, 62, 0, 548, 550, 3, 128, 64, 0, 549, 551, 5, 72, 0, 0, 550, 549, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 5, 68, 0, 0, 553, 554, 5, 100, 0, 0, 554, 559, 3, 128, 64, 0, 555, 556, 5, 63, 0, 0, 556, 558, 3, 128, 64, 0, 557, 555, 1, 0, 0, 0, 558, 561, 1, 0, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 562, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 562, 563, 5, 101, 0, 0, 563, 573, 1, 0, 0, 0, 564, 565, 3, 128, 64, 0, 565, 567, 5, 69, 0, 0, 566, 568, 5, 72, 0, 0, 567, 566, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 5, 73, 0, 0, 570, 573, 1, 0, 0, 0, 571, 573, 3, 126, 63, 0, 572, 543, 1, 0, 0, 0, 572, 546, 1, 0, 0, 0, 572, 547, 1, 0, 0, 0, 572, 548, 1, 0, 0, 0, 572, 564, 1, 0, 0, 0, 572, 571, 1, 0, 0, 0, 573, 582, 1, 0, 0, 0, 574, 575, 10, 5, 0, 0, 575, 576, 5, 56, 0, 0, 576, 581, 3, 122, 61, 6, 577, 578, 10, 4, 0, 0, 578, 579, 5, 76, 0, 0, 579, 581, 3, 122, 61, 5, 580, 574, 1, 0, 0, 0, 580, 577, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 123, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 3, 128, 64, 0, 586, 588, 5, 72, 0, 0, 587, 586, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 5, 71, 0, 0, 590, 591, 3, 152, 76, 0, 591, 600, 1, 0, 0, 0, 592, 594, 3, 128, 64, 0, 593, 595, 5, 72, 0, 0, 594, 593, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 5, 78, 0, 0, 597, 598, 3, 152, 76, 0, 598, 600, 1, 0, 0, 0, 599, 585, 1, 0, 0, 0, 599, 592, 1, 0, 0, 0, 600, 125, 1, 0, 0, 0, 601, 604, 3, 46, 23, 0, 602, 603, 5, 61, 0, 0, 603, 605, 3, 10, 5, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 5, 62, 0, 0, 607, 608, 3, 142, 71, 0, 608, 127, 1, 0, 0, 0, 609, 615, 3, 130, 65, 0, 610, 611, 3, 130, 65, 0, 611, 612, 3, 154, 77, 0, 612, 613, 3, 130, 65, 0, 613, 615, 1, 0, 0, 0, 614, 609, 1, 0, 0, 0, 614, 610, 1, 0, 0, 0, 615, 129, 1, 0, 0, 0, 616, 617, 6, 65, -1, 0, 617, 621, 3, 132, 66, 0, 618, 619, 7, 4, 0, 0, 619, 621, 3, 130, 65, 3, 620, 616, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 630, 1, 0, 0, 0, 622, 623, 10, 2, 0, 0, 623, 624, 7, 5, 0, 0, 624, 629, 3, 130, 65, 3, 625, 626, 10, 1, 0, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 2, 628, 622, 1, 0, 0, 0, 628, 625, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 131, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 6, 66, -1, 0, 634, 642, 3, 142, 71, 0, 635, 642, 3, 46, 23, 0, 636, 642, 3, 134, 67, 0, 637, 638, 5, 100, 0, 0, 638, 639, 3, 122, 61, 0, 639, 640, 5, 101, 0, 0, 640, 642, 1, 0, 0, 0, 641, 633, 1, 0, 0, 0, 641, 635, 1, 0, 0, 0, 641, 636, 1, 0, 0, 0, 641, 637, 1, 0, 0, 0, 642, 648, 1, 0, 0, 0, 643, 644, 10, 1, 0, 0, 644, 645, 5, 61, 0, 0, 645, 647, 3, 10, 5, 0, 646, 643, 1, 0, 0, 0, 647, 650, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 133, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 651, 652, 3, 136, 68, 0, 652, 666, 5, 100, 0, 0, 653, 667, 5, 90, 0, 0, 654, 659, 3, 122, 61, 0, 655, 656, 5, 63, 0, 0, 656, 658, 3, 122, 61, 0, 657, 655, 1, 0, 0, 0, 658, 661, 1, 0, 0, 0, 659, 657, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 664, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 662, 663, 5, 63, 0, 0, 663, 665, 3, 138, 69, 0, 664, 662, 1, 0, 0, 0, 664, 665, 1, 0, 0, 0, 665, 667, 1, 0, 0, 0, 666, 653, 1, 0, 0, 0, 666, 654, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 669, 5, 101, 0, 0, 669, 135, 1, 0, 0, 0, 670, 671, 3, 60, 30, 0, 671, 137, 1, 0, 0, 0, 672, 673, 5, 93, 0, 0, 673, 678, 3, 140, 70, 0, 674, 675, 5, 63, 0, 0, 675, 677, 3, 140, 70, 0, 676, 674, 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, 682, 5, 94, 0, 0, 682, 139, 1, 0, 0, 0, 683, 684, 3, 152, 76, 0, 684, 685, 5, 62, 0, 0, 685, 686, 3, 142, 71, 0, 686, 141, 1, 0, 0, 0, 687, 730, 5, 73, 0, 0, 688, 689, 3, 150, 75, 0, 689, 690, 5, 102, 0, 0, 690, 730, 1, 0, 0, 0, 691, 730, 3, 148, 74, 0, 692, 730, 3, 150, 75, 0, 693, 730, 3, 144, 72, 0, 694, 730, 3, 56, 28, 0, 695, 730, 3, 152, 76, 0, 696, 697, 5, 98, 0, 0, 697, 702, 3, 146, 73, 0, 698, 699, 5, 63, 0, 0, 699, 701, 3, 146, 73, 0, 700, 698, 1, 0, 0, 0, 701, 704, 1, 0, 0, 0, 702, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 705, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 730, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 713, 3, 144, 72, 0, 709, 710, 5, 63, 0, 0, 710, 712, 3, 144, 72, 0, 711, 709, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 99, 0, 0, 717, 730, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 724, 3, 152, 76, 0, 720, 721, 5, 63, 0, 0, 721, 723, 3, 152, 76, 0, 722, 720, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 727, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 728, 5, 99, 0, 0, 728, 730, 1, 0, 0, 0, 729, 687, 1, 0, 0, 0, 729, 688, 1, 0, 0, 0, 729, 691, 1, 0, 0, 0, 729, 692, 1, 0, 0, 0, 729, 693, 1, 0, 0, 0, 729, 694, 1, 0, 0, 0, 729, 695, 1, 0, 0, 0, 729, 696, 1, 0, 0, 0, 729, 707, 1, 0, 0, 0, 729, 718, 1, 0, 0, 0, 730, 143, 1, 0, 0, 0, 731, 732, 7, 6, 0, 0, 732, 145, 1, 0, 0, 0, 733, 736, 3, 148, 74, 0, 734, 736, 3, 150, 75, 0, 735, 733, 1, 0, 0, 0, 735, 734, 1, 0, 0, 0, 736, 147, 1, 0, 0, 0, 737, 739, 7, 4, 0, 0, 738, 737, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 5, 55, 0, 0, 741, 149, 1, 0, 0, 0, 742, 744, 7, 4, 0, 0, 743, 742, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 5, 54, 0, 0, 746, 151, 1, 0, 0, 0, 747, 748, 5, 53, 0, 0, 748, 153, 1, 0, 0, 0, 749, 750, 7, 7, 0, 0, 750, 155, 1, 0, 0, 0, 751, 752, 7, 8, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 3, 158, 79, 0, 754, 755, 3, 160, 80, 0, 755, 157, 1, 0, 0, 0, 756, 757, 3, 28, 14, 0, 757, 159, 1, 0, 0, 0, 758, 759, 5, 75, 0, 0, 759, 764, 3, 162, 81, 0, 760, 761, 5, 63, 0, 0, 761, 763, 3, 162, 81, 0, 762, 760, 1, 0, 0, 0, 763, 766, 1, 0, 0, 0, 764, 762, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 161, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 768, 3, 128, 64, 0, 768, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 550, 559, 567, 572, 580, 582, 587, 594, 599, 604, 614, 620, 628, 630, 641, 648, 659, 664, 666, 678, 702, 713, 724, 729, 735, 738, 743, 764] \ 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 f87031c0a6da0..9d09b0dd50da0 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 @@ -4474,14 +4474,10 @@ public final CompletionCommandContext completionCommand() throws RecognitionExce @SuppressWarnings("CheckReturnValue") public static class SampleCommandContext extends ParserRuleContext { public DecimalValueContext probability; - public IntegerValueContext seed; public TerminalNode DEV_SAMPLE() { return getToken(EsqlBaseParser.DEV_SAMPLE, 0); } public DecimalValueContext decimalValue() { return getRuleContext(DecimalValueContext.class,0); } - public IntegerValueContext integerValue() { - return getRuleContext(IntegerValueContext.class,0); - } @SuppressWarnings("this-escape") public SampleCommandContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); @@ -4512,16 +4508,6 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { match(DEV_SAMPLE); setState(541); ((SampleCommandContext)_localctx).probability = decimalValue(); - setState(543); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,42,_ctx) ) { - case 1: - { - setState(542); - ((SampleCommandContext)_localctx).seed = integerValue(); - } - break; - } } } catch (RecognitionException re) { @@ -4736,18 +4722,18 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(574); + setState(572); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { case 1: { _localctx = new LogicalNotContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(544); match(NOT); - setState(547); + setState(545); booleanExpression(8); } break; @@ -4756,7 +4742,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); + setState(546); valueExpression(); } break; @@ -4765,7 +4751,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(549); + setState(547); regexBooleanExpression(); } break; @@ -4774,41 +4760,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(550); + setState(548); valueExpression(); - setState(552); + setState(550); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(551); + setState(549); match(NOT); } } - setState(554); + setState(552); match(IN); - setState(555); + setState(553); match(LP); - setState(556); + setState(554); valueExpression(); - setState(561); + setState(559); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(557); + setState(555); match(COMMA); - setState(558); + setState(556); valueExpression(); } } - setState(563); + setState(561); _errHandler.sync(this); _la = _input.LA(1); } - setState(564); + setState(562); match(RP); } break; @@ -4817,21 +4803,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(566); + setState(564); valueExpression(); - setState(567); + setState(565); match(IS); - setState(569); + setState(567); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(568); + setState(566); match(NOT); } } - setState(571); + setState(569); match(NULL); } break; @@ -4840,33 +4826,33 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(573); + setState(571); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(584); + setState(582); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,48,_ctx); + _alt = getInterpreter().adaptivePredict(_input,47,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(582); + setState(580); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,47,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: { _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(576); + setState(574); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(577); + setState(575); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(578); + setState(576); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4875,20 +4861,20 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(579); + setState(577); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(580); + setState(578); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(581); + setState(579); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(586); + setState(584); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,48,_ctx); + _alt = getInterpreter().adaptivePredict(_input,47,_ctx); } } } @@ -4941,48 +4927,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(601); + setState(599); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,51,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(587); + setState(585); valueExpression(); - setState(589); + setState(587); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(588); + setState(586); match(NOT); } } - setState(591); + setState(589); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(592); + setState(590); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(594); + setState(592); valueExpression(); - setState(596); + setState(594); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(595); + setState(593); match(NOT); } } - setState(598); + setState(596); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(599); + setState(597); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5042,23 +5028,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(603); + setState(601); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(606); + setState(604); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(604); + setState(602); match(CAST_OP); - setState(605); + setState(603); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(608); + setState(606); match(COLON); - setState(609); + setState(607); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5142,14 +5128,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(616); + setState(614); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(611); + setState(609); operatorExpression(0); } break; @@ -5157,11 +5143,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(612); + setState(610); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(613); + setState(611); comparisonOperator(); - setState(614); + setState(612); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5286,16 +5272,16 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(622); + setState(620); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { case 1: { _localctx = new OperatorExpressionDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(619); + setState(617); primaryExpression(0); } break; @@ -5304,7 +5290,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(620); + setState(618); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5315,31 +5301,31 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(621); + setState(619); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(632); + setState(630); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,56,_ctx); + _alt = getInterpreter().adaptivePredict(_input,55,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(630); + setState(628); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,55,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { case 1: { _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState)); ((ArithmeticBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_operatorExpression); - setState(624); + setState(622); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(625); + setState(623); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 90)) & ~0x3f) == 0 && ((1L << (_la - 90)) & 7L) != 0)) ) { @@ -5350,7 +5336,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(626); + setState(624); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5359,9 +5345,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(627); + setState(625); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(628); + setState(626); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5372,16 +5358,16 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(629); + setState(627); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(634); + setState(632); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,56,_ctx); + _alt = getInterpreter().adaptivePredict(_input,55,_ctx); } } } @@ -5537,16 +5523,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(643); + setState(641); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,57,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { case 1: { _localctx = new ConstantDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(634); constant(); } break; @@ -5555,7 +5541,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(635); qualifiedName(); } break; @@ -5564,7 +5550,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(638); + setState(636); functionExpression(); } break; @@ -5573,19 +5559,19 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(639); + setState(637); match(LP); - setState(640); + setState(638); booleanExpression(0); - setState(641); + setState(639); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(650); + setState(648); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,58,_ctx); + _alt = getInterpreter().adaptivePredict(_input,57,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); @@ -5594,18 +5580,18 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(645); + setState(643); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(646); + setState(644); match(CAST_OP); - setState(647); + setState(645); dataType(); } } } - setState(652); + setState(650); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,58,_ctx); + _alt = getInterpreter().adaptivePredict(_input,57,_ctx); } } } @@ -5669,16 +5655,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(653); + setState(651); functionName(); - setState(654); + setState(652); match(LP); - setState(668); + setState(666); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(655); + setState(653); match(ASTERISK); } break; @@ -5701,34 +5687,34 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(656); + setState(654); booleanExpression(0); - setState(661); + setState(659); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,59,_ctx); + _alt = getInterpreter().adaptivePredict(_input,58,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(657); + setState(655); match(COMMA); - setState(658); + setState(656); booleanExpression(0); } } } - setState(663); + setState(661); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,59,_ctx); + _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } - setState(666); + setState(664); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(664); + setState(662); match(COMMA); - setState(665); + setState(663); mapExpression(); } } @@ -5741,7 +5727,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(670); + setState(668); match(RP); } } @@ -5787,7 +5773,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(670); identifierOrParameter(); } } @@ -5843,27 +5829,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(674); + setState(672); match(LEFT_BRACES); - setState(675); + setState(673); entryExpression(); - setState(680); + setState(678); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(676); + setState(674); match(COMMA); - setState(677); + setState(675); entryExpression(); } } - setState(682); + setState(680); _errHandler.sync(this); _la = _input.LA(1); } - setState(683); + setState(681); match(RIGHT_BRACES); } } @@ -5915,11 +5901,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(685); + setState(683); ((EntryExpressionContext)_localctx).key = string(); - setState(686); + setState(684); match(COLON); - setState(687); + setState(685); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6190,14 +6176,14 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(731); + setState(729); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(689); + setState(687); match(NULL); } break; @@ -6205,9 +6191,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(690); + setState(688); integerValue(); - setState(691); + setState(689); match(UNQUOTED_IDENTIFIER); } break; @@ -6215,7 +6201,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(693); + setState(691); decimalValue(); } break; @@ -6223,7 +6209,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(694); + setState(692); integerValue(); } break; @@ -6231,7 +6217,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(695); + setState(693); booleanValue(); } break; @@ -6239,7 +6225,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(696); + setState(694); parameter(); } break; @@ -6247,7 +6233,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(697); + setState(695); string(); } break; @@ -6255,27 +6241,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(698); + setState(696); match(OPENING_BRACKET); - setState(699); + setState(697); numericValue(); - setState(704); + setState(702); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(700); + setState(698); match(COMMA); - setState(701); + setState(699); numericValue(); } } - setState(706); + setState(704); _errHandler.sync(this); _la = _input.LA(1); } - setState(707); + setState(705); match(CLOSING_BRACKET); } break; @@ -6283,27 +6269,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(709); + setState(707); match(OPENING_BRACKET); - setState(710); + setState(708); booleanValue(); - setState(715); + setState(713); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(711); + setState(709); match(COMMA); - setState(712); + setState(710); booleanValue(); } } - setState(717); + setState(715); _errHandler.sync(this); _la = _input.LA(1); } - setState(718); + setState(716); match(CLOSING_BRACKET); } break; @@ -6311,27 +6297,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(720); + setState(718); match(OPENING_BRACKET); - setState(721); + setState(719); string(); - setState(726); + setState(724); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(722); + setState(720); match(COMMA); - setState(723); + setState(721); string(); } } - setState(728); + setState(726); _errHandler.sync(this); _la = _input.LA(1); } - setState(729); + setState(727); match(CLOSING_BRACKET); } break; @@ -6379,7 +6365,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(733); + setState(731); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6434,20 +6420,20 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(737); + setState(735); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,67,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(735); + setState(733); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(736); + setState(734); integerValue(); } break; @@ -6496,12 +6482,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(740); + setState(738); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(739); + setState(737); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6514,7 +6500,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(742); + setState(740); match(DECIMAL_LITERAL); } } @@ -6561,12 +6547,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(745); + setState(743); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(744); + setState(742); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6579,7 +6565,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(747); + setState(745); match(INTEGER_LITERAL); } } @@ -6623,7 +6609,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(747); match(QUOTED_STRING); } } @@ -6673,7 +6659,7 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(749); _la = _input.LA(1); if ( !(((((_la - 81)) & ~0x3f) == 0 && ((1L << (_la - 81)) & 125L) != 0)) ) { _errHandler.recoverInline(this); @@ -6736,7 +6722,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(753); + setState(751); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6747,11 +6733,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(754); + setState(752); match(JOIN); - setState(755); + setState(753); joinTarget(); - setState(756); + setState(754); joinCondition(); } } @@ -6798,7 +6784,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(758); + setState(756); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6853,27 +6839,27 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(760); + setState(758); match(ON); - setState(761); + setState(759); joinPredicate(); - setState(766); + setState(764); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,70,_ctx); + _alt = getInterpreter().adaptivePredict(_input,69,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(762); + setState(760); match(COMMA); - setState(763); + setState(761); joinPredicate(); } } } - setState(768); + setState(766); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,70,_ctx); + _alt = getInterpreter().adaptivePredict(_input,69,_ctx); } } } @@ -6919,7 +6905,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(769); + setState(767); valueExpression(); } } @@ -7020,7 +7006,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0304\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u0302\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"+ @@ -7089,230 +7075,230 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0001"+ - ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0003<\u0220\b<\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ - ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ - ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ - "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ - "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ - "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ - "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ - "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ - "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ - "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ - "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ - "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ - "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ - "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ - "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ - "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ - "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ - "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ - "5ll\u0001\u0000fg\u0002\u0000::@@\u0002\u0000CCFF\u0001\u0000XY\u0001"+ - "\u0000Z\\\u0002\u0000BBOO\u0002\u0000QQSW\u0002\u0000\u0016\u0016\u0018"+ - "\u0019\u0324\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ - "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ - "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ - "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ - "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ - "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ - "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ - "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ - " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ - "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ - "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ - ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ - "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ - "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ - "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ - "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ - "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ - "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ - "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ - "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ - "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ - "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ - "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ - "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ - "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ - "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ - "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021c"+ - "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ - "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ - "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ - "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ - "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ - "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ - "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ - "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ - "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ - "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ - "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ - "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ - "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ - "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ - "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ - "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ - "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ - "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ - "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ - "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ - "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ - "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ - "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ - "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ - " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ - "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ - "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ - "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ - "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ - "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ - "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ - "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ - "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ - "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ - "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ - "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ - "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ - "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ - "\u0000\u00e2\u00e3\u0005?\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ - "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ - "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ - "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005;\u0000\u0000\u00eb\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ - "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ - "\n\u0000\u00f1\u00f2\u0005?\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ - "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ - "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ - "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005;\u0000\u0000\u00fa\u00fc"+ - "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ - "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ - "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ - "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ - "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ - "\u0104\u0105\u0005?\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ - "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ - "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ - "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ - "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ - "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ - "\u0003\u001e\u000f\u0000\u010f\u0110\u0005>\u0000\u0000\u0110\u0112\u0001"+ - "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ - "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ - "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005=\u0000"+ - "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ - "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ - "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ - "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ - "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ - "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ - "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005k\u0000\u0000\u0122\u0127\u0005"+ - "l\u0000\u0000\u0123\u0124\u0005?\u0000\u0000\u0124\u0126\u0005l\u0000"+ - "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ - "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ - "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ - "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ - "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ - "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ - "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ - "<\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ - "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ - "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005?\u0000\u0000\u0137"+ - "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ - "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ - "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ - "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ - "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ - "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ - "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005A\u0000\u0000\u0144\u0146\u0003"+ - "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ - "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ - "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ - "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005A\u0000\u0000\u014c"+ - "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ - "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005?\u0000"+ - "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ - "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ - "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ - "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ - "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160"+ - "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ - "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ - "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000"+ - "\u0162\u0164\u0005`\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ - "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ - "\u0005_\u0000\u0000\u0166\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000"+ - "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ - "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ - "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ - "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ - "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ - "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ - "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174"+ - "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ - "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ - "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ - "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ - "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ - "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f"+ - "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ - "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ - "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ - "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ - "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ - "\u018e\u0003J%\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003"+ - "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ - "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ - "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ - "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194"+ - "\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b"+ - "\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000"+ - "\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a"+ - "\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005"+ - "\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098"+ - "L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000"+ - "\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7"+ - "\u0003T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ - "\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa"+ - "\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003"+ - "\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000"+ - "\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1"+ - "\u01b2\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4"+ - "\u0005c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!"+ - "\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000"+ - "\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000"+ - "\u01ba\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba"+ - "\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7"+ - "\u0001\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003"+ - "^/\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ + ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0003=\u0227\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ + "=\u0005=\u022e\b=\n=\f=\u0231\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003"+ + "=\u0238\b=\u0001=\u0001=\u0001=\u0003=\u023d\b=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0005=\u0245\b=\n=\f=\u0248\t=\u0001>\u0001>\u0003>\u024c"+ + "\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0253\b>\u0001>\u0001>\u0001"+ + ">\u0003>\u0258\b>\u0001?\u0001?\u0001?\u0003?\u025d\b?\u0001?\u0001?\u0001"+ + "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0267\b@\u0001A\u0001A\u0001"+ + "A\u0001A\u0003A\u026d\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ + "A\u0275\bA\nA\fA\u0278\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0003B\u0282\bB\u0001B\u0001B\u0001B\u0005B\u0287\bB\nB\fB\u028a"+ + "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0292\bC\nC\fC\u0295"+ + "\tC\u0001C\u0001C\u0003C\u0299\bC\u0003C\u029b\bC\u0001C\u0001C\u0001"+ + "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a5\bE\nE\fE\u02a8\tE\u0001"+ + "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02bd"+ + "\bG\nG\fG\u02c0\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c8"+ + "\bG\nG\fG\u02cb\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d3"+ + "\bG\nG\fG\u02d6\tG\u0001G\u0001G\u0003G\u02da\bG\u0001H\u0001H\u0001I"+ + "\u0001I\u0003I\u02e0\bI\u0001J\u0003J\u02e3\bJ\u0001J\u0001J\u0001K\u0003"+ + "K\u02e8\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ + "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u02fb"+ + "\bP\nP\fP\u02fe\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ + "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ + "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ + "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ + "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055ll\u0001\u0000fg\u0002\u0000"+ + "::@@\u0002\u0000CCFF\u0001\u0000XY\u0001\u0000Z\\\u0002\u0000BBOO\u0002"+ + "\u0000QQSW\u0002\u0000\u0016\u0016\u0018\u0019\u0321\u0000\u00a4\u0001"+ + "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ + "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ + "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ + "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ + "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ + "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ + "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ + "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ + "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ + "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ + "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ + "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ + "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ + "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ + ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ + "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ + "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0191\u0001\u0000\u0000\u0000"+ + "L\u0195\u0001\u0000\u0000\u0000N\u019b\u0001\u0000\u0000\u0000P\u019f"+ + "\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000\u0000\u0000T\u01aa\u0001\u0000"+ + "\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000X\u01b1\u0001\u0000\u0000\u0000"+ + "Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8\u0001\u0000\u0000\u0000^\u01cc"+ + "\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000\u0000\u0000b\u01d5\u0001\u0000"+ + "\u0000\u0000d\u01db\u0001\u0000\u0000\u0000f\u01e8\u0001\u0000\u0000\u0000"+ + "h\u01eb\u0001\u0000\u0000\u0000j\u01ef\u0001\u0000\u0000\u0000l\u01f3"+ + "\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000\u0000\u0000p\u0208\u0001\u0000"+ + "\u0000\u0000r\u020a\u0001\u0000\u0000\u0000t\u020c\u0001\u0000\u0000\u0000"+ + "v\u0214\u0001\u0000\u0000\u0000x\u021c\u0001\u0000\u0000\u0000z\u023c"+ + "\u0001\u0000\u0000\u0000|\u0257\u0001\u0000\u0000\u0000~\u0259\u0001\u0000"+ + "\u0000\u0000\u0080\u0266\u0001\u0000\u0000\u0000\u0082\u026c\u0001\u0000"+ + "\u0000\u0000\u0084\u0281\u0001\u0000\u0000\u0000\u0086\u028b\u0001\u0000"+ + "\u0000\u0000\u0088\u029e\u0001\u0000\u0000\u0000\u008a\u02a0\u0001\u0000"+ + "\u0000\u0000\u008c\u02ab\u0001\u0000\u0000\u0000\u008e\u02d9\u0001\u0000"+ + "\u0000\u0000\u0090\u02db\u0001\u0000\u0000\u0000\u0092\u02df\u0001\u0000"+ + "\u0000\u0000\u0094\u02e2\u0001\u0000\u0000\u0000\u0096\u02e7\u0001\u0000"+ + "\u0000\u0000\u0098\u02eb\u0001\u0000\u0000\u0000\u009a\u02ed\u0001\u0000"+ + "\u0000\u0000\u009c\u02ef\u0001\u0000\u0000\u0000\u009e\u02f4\u0001\u0000"+ + "\u0000\u0000\u00a0\u02f6\u0001\u0000\u0000\u0000\u00a2\u02ff\u0001\u0000"+ + "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ + "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ + "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ + "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ + "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ + "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ + "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ + "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ + "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ + "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ + "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ + "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ + "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ + "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ + "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ + "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ + "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ + "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ + "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ + "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ + "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ + "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ + "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ + "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ + "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ + "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ + "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ + "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ + "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005?\u0000"+ + "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ + "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ + "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ + "\u00eb\u0005;\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ + "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ + "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ + "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005?\u0000"+ + "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ + "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ + "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ + "\u00fa\u0005;\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ + "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ + "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ + "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ + "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ + "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005?\u0000\u0000"+ + "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ + "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ + "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ + "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ + "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ + "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ + "\u0110\u0005>\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ + "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ + "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ + "\"\u0011\u0000\u0115\u0116\u0005=\u0000\u0000\u0116\u0118\u0003 \u0010"+ + "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ + "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ + "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ + "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ + "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ + "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ + "\u0122\u0005k\u0000\u0000\u0122\u0127\u0005l\u0000\u0000\u0123\u0124\u0005"+ + "?\u0000\u0000\u0124\u0126\u0005l\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ + "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ + "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ + "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ + "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ + "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ + "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ + "\u0000\u0000\u0000\u0131\u0132\u0005<\u0000\u0000\u0132\u0134\u0003\u000e"+ + "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ + "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ + "\u0136\u0137\u0005?\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ + "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ + "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ + "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ + "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ + "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ + "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ + "\u0144\u0005A\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ + "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ + "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ + "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ + "\u0000\u014b\u014c\u0005A\u0000\u0000\u014c\u014e\u00036\u001b\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"+ + "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ + "\u00030\u0018\u0000\u0153\u0154\u0005?\u0000\u0000\u0154\u0156\u00030"+ + "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ + "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ + "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ + "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ + "\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ + "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ + "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ + "\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000\u0162\u0164\u0005`\u0000"+ + "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ + "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005_\u0000\u0000\u0166"+ + "\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ + "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ + "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ + "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ + "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ + "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ + "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ + "\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174\u0176\u0003B!\u0000"+ + "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ + "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ + "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ + "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ + "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ + "\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f\u0181\u0007\u0003"+ + "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ + "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ + "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ + "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ + "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ + "\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ + "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ + "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ + "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ + "\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194\u00030"+ + "\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b\u0000"+ + "\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000\u0198"+ + "\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a\u0001"+ + "\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005\n"+ + "\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098L\u0000"+ + "\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000\u01a0"+ + "\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7\u0003"+ + "T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ + "S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa\u01ab"+ + "\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003\u008e"+ + "G\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000\u0000"+ + "\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1\u01b2"+ + "\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4\u0005"+ + "c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!\u0000"+ + "\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000\u0000"+ + "\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000\u01ba"+ + "\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba\u0001"+ + "\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7\u0001"+ + "\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003^/"+ + "\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ "\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000\u01c4"+ "\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+ "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7"+ @@ -7361,140 +7347,139 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u0216\u0217\u0005P\u0000\u0000\u0217\u021a\u0003<\u001e\u0000\u0218\u0219"+ "\u00059\u0000\u0000\u0219\u021b\u0003.\u0017\u0000\u021a\u0218\u0001\u0000"+ "\u0000\u0000\u021a\u021b\u0001\u0000\u0000\u0000\u021bw\u0001\u0000\u0000"+ - "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021f\u0003\u0094J\u0000"+ - "\u021e\u0220\u0003\u0096K\u0000\u021f\u021e\u0001\u0000\u0000\u0000\u021f"+ - "\u0220\u0001\u0000\u0000\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222"+ - "\u0006=\uffff\uffff\u0000\u0222\u0223\u0005H\u0000\u0000\u0223\u023f\u0003"+ - "z=\b\u0224\u023f\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228"+ - "\u0003\u0080@\u0000\u0227\u0229\u0005H\u0000\u0000\u0228\u0227\u0001\u0000"+ - "\u0000\u0000\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000"+ - "\u0000\u0000\u022a\u022b\u0005D\u0000\u0000\u022b\u022c\u0005d\u0000\u0000"+ - "\u022c\u0231\u0003\u0080@\u0000\u022d\u022e\u0005?\u0000\u0000\u022e\u0230"+ - "\u0003\u0080@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001"+ - "\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001"+ - "\u0000\u0000\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001"+ - "\u0000\u0000\u0000\u0234\u0235\u0005e\u0000\u0000\u0235\u023f\u0001\u0000"+ - "\u0000\u0000\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005E\u0000\u0000"+ - "\u0238\u023a\u0005H\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239"+ - "\u023a\u0001\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b"+ - "\u023c\u0005I\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f"+ - "\u0003~?\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000"+ - "\u0000\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000"+ - "\u0000\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000"+ - "\u0000\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000"+ - "\u0000\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243"+ - "\u0244\n\u0004\u0000\u0000\u0244\u0245\u0005L\u0000\u0000\u0245\u0247"+ - "\u0003z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000"+ - "\u0000\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000"+ - "\u0000\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000"+ - "\u0000\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000"+ - "\u024c\u024e\u0005H\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d"+ - "\u024e\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f"+ - "\u0250\u0005G\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001"+ - "\u0000\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005H\u0000"+ - "\u0000\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000"+ - "\u0000\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005N\u0000\u0000"+ - "\u0257\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259"+ - "\u024b\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a"+ - "}\u0001\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005"+ - "=\u0000\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000"+ - "\u0000\u0000\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000"+ - "\u0000\u0000\u0260\u0261\u0005>\u0000\u0000\u0261\u0262\u0003\u008eG\u0000"+ - "\u0262\u007f\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264"+ - "\u0265\u0003\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003"+ - "\u0082A\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000"+ - "\u0000\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000"+ - "\u0000\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084"+ - "B\u0000\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003"+ - "\u026e\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000"+ - "\u026f\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271"+ - "\u0272\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274"+ - "\n\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ - "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ - "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ - "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ - "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ - "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ - "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005d\u0000\u0000\u0280\u0281"+ - "\u0003z=\u0000\u0281\u0282\u0005e\u0000\u0000\u0282\u0284\u0001\u0000"+ - "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ - "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ - "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ - "\u0000\u0286\u0287\u0005=\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ - "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ - "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ - "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ - "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005d\u0000\u0000\u028f\u029d"+ - "\u0005Z\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005?\u0000"+ - "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ - "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ - "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ - "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005?\u0000\u0000\u0299\u029b"+ - "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ - "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ - "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ - "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ - "e\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ - "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005]\u0000\u0000"+ - "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005?\u0000\u0000\u02a5\u02a7"+ - "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ - "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ - "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ - "\u0000\u0000\u0000\u02ab\u02ac\u0005^\u0000\u0000\u02ac\u008b\u0001\u0000"+ - "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005>\u0000\u0000"+ - "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ - "\u02dc\u0005I\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ - "f\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ - "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ - "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ - "\u0005b\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005?"+ - "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ - "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ - "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ - "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005c\u0000\u0000"+ - "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005b\u0000\u0000\u02c6"+ - "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005?\u0000\u0000\u02c8\u02ca\u0003"+ - "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ - "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ - "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ - "\u0000\u0000\u02ce\u02cf\u0005c\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ - "\u0000\u02d0\u02d1\u0005b\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ - "\u02d3\u0005?\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ - "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ - "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ - "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ - "c\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ - "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ - "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ - "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ - "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ - "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ - "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ - "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ - "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ - "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ - "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ - "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ - "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ - "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ - "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ - "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ - "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ - "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005s"+ - "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ - "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ - "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005K\u0000\u0000\u02f9"+ - "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005?\u0000\u0000\u02fb\u02fd\u0003"+ - "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ - "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ - "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ - "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ - "\u0000G\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ - "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ - "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ - "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u021a\u021f\u0228\u0231\u0239\u023e"+ - "\u0246\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a"+ - "\u0295\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; + "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021e\u0003\u0094J\u0000"+ + "\u021ey\u0001\u0000\u0000\u0000\u021f\u0220\u0006=\uffff\uffff\u0000\u0220"+ + "\u0221\u0005H\u0000\u0000\u0221\u023d\u0003z=\b\u0222\u023d\u0003\u0080"+ + "@\u0000\u0223\u023d\u0003|>\u0000\u0224\u0226\u0003\u0080@\u0000\u0225"+ + "\u0227\u0005H\u0000\u0000\u0226\u0225\u0001\u0000\u0000\u0000\u0226\u0227"+ + "\u0001\u0000\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228\u0229"+ + "\u0005D\u0000\u0000\u0229\u022a\u0005d\u0000\u0000\u022a\u022f\u0003\u0080"+ + "@\u0000\u022b\u022c\u0005?\u0000\u0000\u022c\u022e\u0003\u0080@\u0000"+ + "\u022d\u022b\u0001\u0000\u0000\u0000\u022e\u0231\u0001\u0000\u0000\u0000"+ + "\u022f\u022d\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000"+ + "\u0230\u0232\u0001\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000"+ + "\u0232\u0233\u0005e\u0000\u0000\u0233\u023d\u0001\u0000\u0000\u0000\u0234"+ + "\u0235\u0003\u0080@\u0000\u0235\u0237\u0005E\u0000\u0000\u0236\u0238\u0005"+ + "H\u0000\u0000\u0237\u0236\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000"+ + "\u0000\u0000\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0005I\u0000"+ + "\u0000\u023a\u023d\u0001\u0000\u0000\u0000\u023b\u023d\u0003~?\u0000\u023c"+ + "\u021f\u0001\u0000\u0000\u0000\u023c\u0222\u0001\u0000\u0000\u0000\u023c"+ + "\u0223\u0001\u0000\u0000\u0000\u023c\u0224\u0001\u0000\u0000\u0000\u023c"+ + "\u0234\u0001\u0000\u0000\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d"+ + "\u0246\u0001\u0000\u0000\u0000\u023e\u023f\n\u0005\u0000\u0000\u023f\u0240"+ + "\u00058\u0000\u0000\u0240\u0245\u0003z=\u0006\u0241\u0242\n\u0004\u0000"+ + "\u0000\u0242\u0243\u0005L\u0000\u0000\u0243\u0245\u0003z=\u0005\u0244"+ + "\u023e\u0001\u0000\u0000\u0000\u0244\u0241\u0001\u0000\u0000\u0000\u0245"+ + "\u0248\u0001\u0000\u0000\u0000\u0246\u0244\u0001\u0000\u0000\u0000\u0246"+ + "\u0247\u0001\u0000\u0000\u0000\u0247{\u0001\u0000\u0000\u0000\u0248\u0246"+ + "\u0001\u0000\u0000\u0000\u0249\u024b\u0003\u0080@\u0000\u024a\u024c\u0005"+ + "H\u0000\u0000\u024b\u024a\u0001\u0000\u0000\u0000\u024b\u024c\u0001\u0000"+ + "\u0000\u0000\u024c\u024d\u0001\u0000\u0000\u0000\u024d\u024e\u0005G\u0000"+ + "\u0000\u024e\u024f\u0003\u0098L\u0000\u024f\u0258\u0001\u0000\u0000\u0000"+ + "\u0250\u0252\u0003\u0080@\u0000\u0251\u0253\u0005H\u0000\u0000\u0252\u0251"+ + "\u0001\u0000\u0000\u0000\u0252\u0253\u0001\u0000\u0000\u0000\u0253\u0254"+ + "\u0001\u0000\u0000\u0000\u0254\u0255\u0005N\u0000\u0000\u0255\u0256\u0003"+ + "\u0098L\u0000\u0256\u0258\u0001\u0000\u0000\u0000\u0257\u0249\u0001\u0000"+ + "\u0000\u0000\u0257\u0250\u0001\u0000\u0000\u0000\u0258}\u0001\u0000\u0000"+ + "\u0000\u0259\u025c\u0003.\u0017\u0000\u025a\u025b\u0005=\u0000\u0000\u025b"+ + "\u025d\u0003\n\u0005\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025c\u025d"+ + "\u0001\u0000\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f"+ + "\u0005>\u0000\u0000\u025f\u0260\u0003\u008eG\u0000\u0260\u007f\u0001\u0000"+ + "\u0000\u0000\u0261\u0267\u0003\u0082A\u0000\u0262\u0263\u0003\u0082A\u0000"+ + "\u0263\u0264\u0003\u009aM\u0000\u0264\u0265\u0003\u0082A\u0000\u0265\u0267"+ + "\u0001\u0000\u0000\u0000\u0266\u0261\u0001\u0000\u0000\u0000\u0266\u0262"+ + "\u0001\u0000\u0000\u0000\u0267\u0081\u0001\u0000\u0000\u0000\u0268\u0269"+ + "\u0006A\uffff\uffff\u0000\u0269\u026d\u0003\u0084B\u0000\u026a\u026b\u0007"+ + "\u0004\u0000\u0000\u026b\u026d\u0003\u0082A\u0003\u026c\u0268\u0001\u0000"+ + "\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026d\u0276\u0001\u0000"+ + "\u0000\u0000\u026e\u026f\n\u0002\u0000\u0000\u026f\u0270\u0007\u0005\u0000"+ + "\u0000\u0270\u0275\u0003\u0082A\u0003\u0271\u0272\n\u0001\u0000\u0000"+ + "\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0002\u0274"+ + "\u026e\u0001\u0000\u0000\u0000\u0274\u0271\u0001\u0000\u0000\u0000\u0275"+ + "\u0278\u0001\u0000\u0000\u0000\u0276\u0274\u0001\u0000\u0000\u0000\u0276"+ + "\u0277\u0001\u0000\u0000\u0000\u0277\u0083\u0001\u0000\u0000\u0000\u0278"+ + "\u0276\u0001\u0000\u0000\u0000\u0279\u027a\u0006B\uffff\uffff\u0000\u027a"+ + "\u0282\u0003\u008eG\u0000\u027b\u0282\u0003.\u0017\u0000\u027c\u0282\u0003"+ + "\u0086C\u0000\u027d\u027e\u0005d\u0000\u0000\u027e\u027f\u0003z=\u0000"+ + "\u027f\u0280\u0005e\u0000\u0000\u0280\u0282\u0001\u0000\u0000\u0000\u0281"+ + "\u0279\u0001\u0000\u0000\u0000\u0281\u027b\u0001\u0000\u0000\u0000\u0281"+ + "\u027c\u0001\u0000\u0000\u0000\u0281\u027d\u0001\u0000\u0000\u0000\u0282"+ + "\u0288\u0001\u0000\u0000\u0000\u0283\u0284\n\u0001\u0000\u0000\u0284\u0285"+ + "\u0005=\u0000\u0000\u0285\u0287\u0003\n\u0005\u0000\u0286\u0283\u0001"+ + "\u0000\u0000\u0000\u0287\u028a\u0001\u0000\u0000\u0000\u0288\u0286\u0001"+ + "\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000\u0289\u0085\u0001"+ + "\u0000\u0000\u0000\u028a\u0288\u0001\u0000\u0000\u0000\u028b\u028c\u0003"+ + "\u0088D\u0000\u028c\u029a\u0005d\u0000\u0000\u028d\u029b\u0005Z\u0000"+ + "\u0000\u028e\u0293\u0003z=\u0000\u028f\u0290\u0005?\u0000\u0000\u0290"+ + "\u0292\u0003z=\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0295\u0001"+ + "\u0000\u0000\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0293\u0294\u0001"+ + "\u0000\u0000\u0000\u0294\u0298\u0001\u0000\u0000\u0000\u0295\u0293\u0001"+ + "\u0000\u0000\u0000\u0296\u0297\u0005?\u0000\u0000\u0297\u0299\u0003\u008a"+ + "E\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0298\u0299\u0001\u0000\u0000"+ + "\u0000\u0299\u029b\u0001\u0000\u0000\u0000\u029a\u028d\u0001\u0000\u0000"+ + "\u0000\u029a\u028e\u0001\u0000\u0000\u0000\u029a\u029b\u0001\u0000\u0000"+ + "\u0000\u029b\u029c\u0001\u0000\u0000\u0000\u029c\u029d\u0005e\u0000\u0000"+ + "\u029d\u0087\u0001\u0000\u0000\u0000\u029e\u029f\u0003<\u001e\u0000\u029f"+ + "\u0089\u0001\u0000\u0000\u0000\u02a0\u02a1\u0005]\u0000\u0000\u02a1\u02a6"+ + "\u0003\u008cF\u0000\u02a2\u02a3\u0005?\u0000\u0000\u02a3\u02a5\u0003\u008c"+ + "F\u0000\u02a4\u02a2\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\u02aa\u0005^\u0000\u0000\u02aa\u008b\u0001\u0000\u0000\u0000"+ + "\u02ab\u02ac\u0003\u0098L\u0000\u02ac\u02ad\u0005>\u0000\u0000\u02ad\u02ae"+ + "\u0003\u008eG\u0000\u02ae\u008d\u0001\u0000\u0000\u0000\u02af\u02da\u0005"+ + "I\u0000\u0000\u02b0\u02b1\u0003\u0096K\u0000\u02b1\u02b2\u0005f\u0000"+ + "\u0000\u02b2\u02da\u0001\u0000\u0000\u0000\u02b3\u02da\u0003\u0094J\u0000"+ + "\u02b4\u02da\u0003\u0096K\u0000\u02b5\u02da\u0003\u0090H\u0000\u02b6\u02da"+ + "\u00038\u001c\u0000\u02b7\u02da\u0003\u0098L\u0000\u02b8\u02b9\u0005b"+ + "\u0000\u0000\u02b9\u02be\u0003\u0092I\u0000\u02ba\u02bb\u0005?\u0000\u0000"+ + "\u02bb\u02bd\u0003\u0092I\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd"+ + "\u02c0\u0001\u0000\u0000\u0000\u02be\u02bc\u0001\u0000\u0000\u0000\u02be"+ + "\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c1\u0001\u0000\u0000\u0000\u02c0"+ + "\u02be\u0001\u0000\u0000\u0000\u02c1\u02c2\u0005c\u0000\u0000\u02c2\u02da"+ + "\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000\u02c4\u02c9\u0003"+ + "\u0090H\u0000\u02c5\u02c6\u0005?\u0000\u0000\u02c6\u02c8\u0003\u0090H"+ + "\u0000\u02c7\u02c5\u0001\u0000\u0000\u0000\u02c8\u02cb\u0001\u0000\u0000"+ + "\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000"+ + "\u0000\u02ca\u02cc\u0001\u0000\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000"+ + "\u0000\u02cc\u02cd\u0005c\u0000\u0000\u02cd\u02da\u0001\u0000\u0000\u0000"+ + "\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02d4\u0003\u0098L\u0000\u02d0\u02d1"+ + "\u0005?\u0000\u0000\u02d1\u02d3\u0003\u0098L\u0000\u02d2\u02d0\u0001\u0000"+ + "\u0000\u0000\u02d3\u02d6\u0001\u0000\u0000\u0000\u02d4\u02d2\u0001\u0000"+ + "\u0000\u0000\u02d4\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d7\u0001\u0000"+ + "\u0000\u0000\u02d6\u02d4\u0001\u0000\u0000\u0000\u02d7\u02d8\u0005c\u0000"+ + "\u0000\u02d8\u02da\u0001\u0000\u0000\u0000\u02d9\u02af\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b0\u0001\u0000\u0000\u0000\u02d9\u02b3\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b4\u0001\u0000\u0000\u0000\u02d9\u02b5\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b6\u0001\u0000\u0000\u0000\u02d9\u02b7\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b8\u0001\u0000\u0000\u0000\u02d9\u02c3\u0001\u0000\u0000"+ + "\u0000\u02d9\u02ce\u0001\u0000\u0000\u0000\u02da\u008f\u0001\u0000\u0000"+ + "\u0000\u02db\u02dc\u0007\u0006\u0000\u0000\u02dc\u0091\u0001\u0000\u0000"+ + "\u0000\u02dd\u02e0\u0003\u0094J\u0000\u02de\u02e0\u0003\u0096K\u0000\u02df"+ + "\u02dd\u0001\u0000\u0000\u0000\u02df\u02de\u0001\u0000\u0000\u0000\u02e0"+ + "\u0093\u0001\u0000\u0000\u0000\u02e1\u02e3\u0007\u0004\u0000\u0000\u02e2"+ + "\u02e1\u0001\u0000\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ + "\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u00057\u0000\u0000\u02e5\u0095"+ + "\u0001\u0000\u0000\u0000\u02e6\u02e8\u0007\u0004\u0000\u0000\u02e7\u02e6"+ + "\u0001\u0000\u0000\u0000\u02e7\u02e8\u0001\u0000\u0000\u0000\u02e8\u02e9"+ + "\u0001\u0000\u0000\u0000\u02e9\u02ea\u00056\u0000\u0000\u02ea\u0097\u0001"+ + "\u0000\u0000\u0000\u02eb\u02ec\u00055\u0000\u0000\u02ec\u0099\u0001\u0000"+ + "\u0000\u0000\u02ed\u02ee\u0007\u0007\u0000\u0000\u02ee\u009b\u0001\u0000"+ + "\u0000\u0000\u02ef\u02f0\u0007\b\u0000\u0000\u02f0\u02f1\u0005s\u0000"+ + "\u0000\u02f1\u02f2\u0003\u009eO\u0000\u02f2\u02f3\u0003\u00a0P\u0000\u02f3"+ + "\u009d\u0001\u0000\u0000\u0000\u02f4\u02f5\u0003\u001c\u000e\u0000\u02f5"+ + "\u009f\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005K\u0000\u0000\u02f7\u02fc"+ + "\u0003\u00a2Q\u0000\u02f8\u02f9\u0005?\u0000\u0000\u02f9\u02fb\u0003\u00a2"+ + "Q\u0000\u02fa\u02f8\u0001\u0000\u0000\u0000\u02fb\u02fe\u0001\u0000\u0000"+ + "\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fc\u02fd\u0001\u0000\u0000"+ + "\u0000\u02fd\u00a1\u0001\u0000\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000"+ + "\u0000\u02ff\u0300\u0003\u0080@\u0000\u0300\u00a3\u0001\u0000\u0000\u0000"+ + "F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117\u0119"+ + "\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167\u016c"+ + "\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9\u01df"+ + "\u01e6\u01f1\u01ff\u0208\u0212\u021a\u0226\u022f\u0237\u023c\u0244\u0246"+ + "\u024b\u0252\u0257\u025c\u0266\u026c\u0274\u0276\u0281\u0288\u0293\u0298"+ + "\u029a\u02a6\u02be\u02c9\u02d4\u02d9\u02df\u02e2\u02e7\u02fc"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java index d62017aded35b..746dd315069f2 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 @@ -784,20 +784,6 @@ public Literal inferenceId(EsqlBaseParser.IdentifierOrParameterContext ctx) { public PlanFactory visitSampleCommand(EsqlBaseParser.SampleCommandContext ctx) { var probability = visitDecimalValue(ctx.probability); - Literal seed; - if (ctx.seed != null) { - seed = visitIntegerValue(ctx.seed); - if (seed.dataType() != DataType.INTEGER) { - throw new ParsingException( - seed.source(), - "seed must be an integer, provided [{}] of type [{}]", - ctx.seed.getText(), - seed.dataType() - ); - } - } else { - seed = null; - } - return plan -> new Sample(source(ctx), probability, seed, plan); + return plan -> new Sample(source(ctx), probability, plan); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Sample.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Sample.java index ea4e9396ecca8..e85d77b3c0f39 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Sample.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Sample.java @@ -9,7 +9,6 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.core.Nullable; import org.elasticsearch.search.aggregations.bucket.sampler.random.RandomSamplingQuery; import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware; import org.elasticsearch.xpack.esql.capabilities.TelemetryAware; @@ -30,19 +29,16 @@ public class Sample extends UnaryPlan implements TelemetryAware, PostAnalysisVer public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(LogicalPlan.class, "Sample", Sample::new); private final Expression probability; - private final Expression seed; - public Sample(Source source, Expression probability, @Nullable Expression seed, LogicalPlan child) { + public Sample(Source source, Expression probability, LogicalPlan child) { super(source, child); this.probability = probability; - this.seed = seed; } private Sample(StreamInput in) throws IOException { this( Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class), // probability - in.readOptionalNamedWriteable(Expression.class), // seed in.readNamedWriteable(LogicalPlan.class) // child ); } @@ -51,7 +47,6 @@ private Sample(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { source().writeTo(out); out.writeNamedWriteable(probability); - out.writeOptionalNamedWriteable(seed); out.writeNamedWriteable(child()); } @@ -62,30 +57,26 @@ public String getWriteableName() { @Override protected NodeInfo info() { - return NodeInfo.create(this, Sample::new, probability, seed, child()); + return NodeInfo.create(this, Sample::new, probability, child()); } @Override public Sample replaceChild(LogicalPlan newChild) { - return new Sample(source(), probability, seed, newChild); + return new Sample(source(), probability, newChild); } public Expression probability() { return probability; } - public Expression seed() { - return seed; - } - @Override public boolean expressionsResolved() { - return probability.resolved() && (seed == null || seed.resolved()); + return probability.resolved(); } @Override public int hashCode() { - return Objects.hash(probability, seed, child()); + return Objects.hash(probability, child()); } @Override @@ -99,7 +90,7 @@ public boolean equals(Object obj) { var other = (Sample) obj; - return Objects.equals(probability, other.probability) && Objects.equals(seed, other.seed) && Objects.equals(child(), other.child()); + return Objects.equals(probability, other.probability) && Objects.equals(child(), other.child()); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/SampleExec.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/SampleExec.java index e110a1b60928a..25dbf633713da 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/SampleExec.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/SampleExec.java @@ -10,9 +10,6 @@ import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.xpack.esql.capabilities.PostPhysicalOptimizationVerificationAware; -import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; @@ -21,9 +18,7 @@ import java.io.IOException; import java.util.Objects; -import static org.elasticsearch.xpack.esql.common.Failure.fail; - -public class SampleExec extends UnaryExec implements PostPhysicalOptimizationVerificationAware { +public class SampleExec extends UnaryExec { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( PhysicalPlan.class, "SampleExec", @@ -31,20 +26,17 @@ public class SampleExec extends UnaryExec implements PostPhysicalOptimizationVer ); private final Expression probability; - private final Expression seed; - public SampleExec(Source source, PhysicalPlan child, Expression probability, @Nullable Expression seed) { + public SampleExec(Source source, PhysicalPlan child, Expression probability) { super(source, child); this.probability = probability; - this.seed = seed; } public SampleExec(StreamInput in) throws IOException { this( Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(PhysicalPlan.class), // child - in.readNamedWriteable(Expression.class), // probability - in.readOptionalNamedWriteable(Expression.class) // seed + in.readNamedWriteable(Expression.class) // probability ); } @@ -53,17 +45,16 @@ public void writeTo(StreamOutput out) throws IOException { source().writeTo(out); out.writeNamedWriteable(child()); out.writeNamedWriteable(probability); - out.writeOptionalNamedWriteable(seed); } @Override public UnaryExec replaceChild(PhysicalPlan newChild) { - return new SampleExec(source(), newChild, probability, seed); + return new SampleExec(source(), newChild, probability); } @Override protected NodeInfo info() { - return NodeInfo.create(this, SampleExec::new, child(), probability, seed); + return NodeInfo.create(this, SampleExec::new, child(), probability); } /** @@ -78,13 +69,9 @@ public Expression probability() { return probability; } - public Expression seed() { - return seed; - } - @Override public int hashCode() { - return Objects.hash(child(), probability, seed); + return Objects.hash(child(), probability); } @Override @@ -98,17 +85,6 @@ public boolean equals(Object obj) { var other = (SampleExec) obj; - return Objects.equals(child(), other.child()) && Objects.equals(probability, other.probability) && Objects.equals(seed, other.seed); - } - - @Override - public void postPhysicalOptimizationVerification(Failures failures) { - // It's currently impossible in ES|QL to handle all data in deterministic order, therefore - // a fixed random seed in the sample operator doesn't work as intended and is disallowed. - // TODO: fix this. - if (seed != null) { - // TODO: what should the error message here be? This doesn't seem right. - failures.add(fail(seed, "Seed not supported when sampling can't be pushed down to Lucene")); - } + return Objects.equals(child(), other.child()) && Objects.equals(probability, other.probability); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlanner.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlanner.java index a4a419cd5646a..db2862234c136 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlanner.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlanner.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.esql.planner; import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.Randomness; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; @@ -868,8 +867,7 @@ private PhysicalOperation planChangePoint(ChangePointExec changePoint, LocalExec private PhysicalOperation planSample(SampleExec rsx, LocalExecutionPlannerContext context) { PhysicalOperation source = plan(rsx.child(), context); var probability = (double) Foldables.valueOf(context.foldCtx(), rsx.probability()); - var seed = rsx.seed() != null ? (int) Foldables.valueOf(context.foldCtx(), rsx.seed()) : Randomness.get().nextInt(); - return source.with(new SampleOperator.Factory(probability, seed), source.layout); + return source.with(new SampleOperator.Factory(probability), source.layout); } /** diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/LocalMapper.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/LocalMapper.java index f0064178a57f2..29f2db102ea7e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/LocalMapper.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/LocalMapper.java @@ -86,7 +86,7 @@ private PhysicalPlan mapUnary(UnaryPlan unary) { } if (unary instanceof Sample sample) { - return new SampleExec(sample.source(), mappedChild, sample.probability(), sample.seed()); + return new SampleExec(sample.source(), mappedChild, sample.probability()); } // diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java index 5586140d809ea..137a2118b0d54 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java @@ -191,7 +191,7 @@ private PhysicalPlan mapUnary(UnaryPlan unary) { // TODO: share code with local LocalMapper? if (unary instanceof Sample sample) { mappedChild = addExchangeForFragment(sample, mappedChild); - return new SampleExec(sample.source(), mappedChild, sample.probability(), sample.seed()); + return new SampleExec(sample.source(), mappedChild, sample.probability()); } // diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index 367fe7706f5e8..635abae133d20 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -7862,9 +7862,9 @@ public void testSampleMerged() { var query = """ FROM TEST - | SAMPLE .3 5 + | SAMPLE .3 | EVAL irrelevant1 = 1 - | SAMPLE .5 10 + | SAMPLE .5 | EVAL irrelevant2 = 2 | SAMPLE .1 """; @@ -7876,7 +7876,6 @@ public void testSampleMerged() { var source = as(sample.child(), EsRelation.class); assertThat(sample.probability().fold(FoldContext.small()), equalTo(0.015)); - assertThat(sample.seed().fold(FoldContext.small()), equalTo(5 ^ 10)); } public void testSamplePushDown() { @@ -7901,7 +7900,6 @@ public void testSamplePushDown() { var source = as(sample.child(), EsRelation.class); assertThat(sample.probability().fold(FoldContext.small()), equalTo(0.5)); - assertNull(sample.seed()); } } @@ -7917,7 +7915,6 @@ public void testSamplePushDown_sort() { var source = as(sample.child(), EsRelation.class); assertThat(sample.probability().fold(FoldContext.small()), equalTo(0.5)); - assertNull(sample.seed()); } public void testSamplePushDown_where() { @@ -7931,7 +7928,6 @@ public void testSamplePushDown_where() { var source = as(sample.child(), EsRelation.class); assertThat(sample.probability().fold(FoldContext.small()), equalTo(0.5)); - assertNull(sample.seed()); } public void testSampleNoPushDown() { @@ -7988,7 +7984,7 @@ public void testSampleNoPushDownChangePoint() { var query = """ FROM TEST | CHANGE_POINT emp_no ON hire_date - | SAMPLE .5 -55 + | SAMPLE .5 """; var optimized = optimizedPlan(query); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index 8aeef36e8d9c1..2d47498b7e0d3 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -8049,7 +8049,7 @@ public void testSamplePushDown() { var plan = physicalPlan(""" FROM test - | SAMPLE +0.1 -234 + | SAMPLE +0.1 """); var optimized = optimizedPlan(plan); @@ -8063,24 +8063,9 @@ public void testSamplePushDown() { var filter = boolQuery.filter(); var randomSampling = as(filter.get(0), RandomSamplingQueryBuilder.class); assertThat(randomSampling.probability(), equalTo(0.1)); - assertThat(randomSampling.seed(), equalTo(-234)); assertThat(randomSampling.hash(), equalTo(0)); } - public void testSample_seedNotSupportedInOperator() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); - - optimizedPlan(physicalPlan("FROM test | SAMPLE 0.1")); - optimizedPlan(physicalPlan("FROM test | SAMPLE 0.1 42")); - optimizedPlan(physicalPlan("FROM test | MV_EXPAND first_name | SAMPLE 0.1")); - - VerificationException e = expectThrows( - VerificationException.class, - () -> optimizedPlan(physicalPlan("FROM test | MV_EXPAND first_name | SAMPLE 0.1 42")) - ); - assertThat(e.getMessage(), equalTo("Found 1 problem\nline 1:47: Seed not supported when sampling can't be pushed down to Lucene")); - } - @SuppressWarnings("SameParameterValue") private static void assertFilterCondition( Filter filter, 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 3d50a109eded4..60851771dad8b 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 @@ -3483,11 +3483,10 @@ public void testInvalidCompletion() { public void testSample() { assumeTrue("SAMPLE requires corresponding capability", EsqlCapabilities.Cap.SAMPLE.isEnabled()); - expectError("FROM test | SAMPLE .1 2 3", "line 1:25: extraneous input '3' expecting "); + expectError("FROM test | SAMPLE .1 2", "line 1:23: extraneous input '2' expecting "); expectError("FROM test | SAMPLE .1 \"2\"", "line 1:23: extraneous input '\"2\"' expecting "); expectError("FROM test | SAMPLE 1", "line 1:20: mismatched input '1' expecting {DECIMAL_LITERAL, '+', '-'}"); expectError("FROM test | SAMPLE", "line 1:19: mismatched input '' expecting {DECIMAL_LITERAL, '+', '-'}"); - expectError("FROM test | SAMPLE +.1 2147483648", "line 1:24: seed must be an integer, provided [2147483648] of type [LONG]"); } static Alias alias(String name, Expression value) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java index ab4e2f0b3e0d2..343af17bf05f3 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/CommandLicenseTests.java @@ -159,7 +159,7 @@ private static LogicalPlan createInstance(Class clazz, Lo return new Fork(source, List.of(child, child), List.of()); } case "Sample" -> { - return new Sample(source, null, null, child); + return new Sample(source, null, child); } case "LookupJoin" -> { return new LookupJoin(source, child, child, List.of()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/SampleSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/SampleSerializationTests.java index f24d738789b3e..f5303a12dde72 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/SampleSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/SampleSerializationTests.java @@ -20,7 +20,7 @@ public class SampleSerializationTests extends AbstractLogicalPlanSerializationTe */ @Override protected Sample createTestInstance() { - return new Sample(randomSource(), randomProbability(), randomSeed(), randomChild(0)); + return new Sample(randomSource(), randomProbability(), randomChild(0)); } public static Literal randomProbability() { @@ -40,15 +40,13 @@ public static Literal randomSeed() { @Override protected Sample mutateInstance(Sample instance) throws IOException { var probability = instance.probability(); - var seed = instance.seed(); var child = instance.child(); - int updateSelector = randomIntBetween(0, 2); + int updateSelector = randomIntBetween(0, 1); switch (updateSelector) { case 0 -> probability = randomValueOtherThan(probability, SampleSerializationTests::randomProbability); - case 1 -> seed = randomValueOtherThan(seed, SampleSerializationTests::randomSeed); - case 2 -> child = randomValueOtherThan(child, () -> randomChild(0)); + case 1 -> child = randomValueOtherThan(child, () -> randomChild(0)); default -> throw new IllegalArgumentException("Invalid selector: " + updateSelector); } - return new Sample(instance.source(), probability, seed, child); + return new Sample(instance.source(), probability, child); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/SampleExecSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/SampleExecSerializationTests.java index 12159d8afae7c..385e2e37aa140 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/SampleExecSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/SampleExecSerializationTests.java @@ -12,7 +12,6 @@ import java.io.IOException; import static org.elasticsearch.xpack.esql.plan.logical.SampleSerializationTests.randomProbability; -import static org.elasticsearch.xpack.esql.plan.logical.SampleSerializationTests.randomSeed; public class SampleExecSerializationTests extends AbstractPhysicalPlanSerializationTests { /** @@ -22,7 +21,7 @@ public class SampleExecSerializationTests extends AbstractPhysicalPlanSerializat */ @Override protected SampleExec createTestInstance() { - return new SampleExec(randomSource(), randomChild(0), randomProbability(), randomSeed()); + return new SampleExec(randomSource(), randomChild(0), randomProbability()); } /** @@ -34,15 +33,13 @@ protected SampleExec createTestInstance() { @Override protected SampleExec mutateInstance(SampleExec instance) throws IOException { var probability = instance.probability(); - var seed = instance.seed(); var child = instance.child(); - int updateSelector = randomIntBetween(0, 2); + int updateSelector = randomIntBetween(0, 1); switch (updateSelector) { case 0 -> probability = randomValueOtherThan(probability, SampleSerializationTests::randomProbability); - case 1 -> seed = randomValueOtherThan(seed, SampleSerializationTests::randomSeed); - case 2 -> child = randomValueOtherThan(child, () -> randomChild(0)); + case 1 -> child = randomValueOtherThan(child, () -> randomChild(0)); default -> throw new IllegalArgumentException("Invalid selector: " + updateSelector); } - return new SampleExec(instance.source(), child, probability, seed); + return new SampleExec(instance.source(), child, probability); } } From 7d37afa2123a3c05a5c58ed2dc5cae75cd5abf58 Mon Sep 17 00:00:00 2001 From: Tim Grein Date: Tue, 10 Jun 2025 12:12:51 +0200 Subject: [PATCH 029/102] [Inference API] Add "rerank" task type to "elastic" provider (#126022) --- .../org/elasticsearch/TransportVersions.java | 2 + .../InferenceNamedWriteablesProvider.java | 17 +- .../ElasticInferenceServiceRerankRequest.java | 94 +++++++++++ ...icInferenceServiceRerankRequestEntity.java | 59 +++++++ ...cInferenceServiceRerankResponseEntity.java | 70 +++++++++ .../elastic/ElasticInferenceService.java | 46 ++++-- .../elastic/ElasticInferenceServiceModel.java | 17 +- ...ElasticInferenceServiceRequestManager.java | 4 +- ...ServiceSparseEmbeddingsRequestManager.java | 1 + .../ElasticInferenceServiceActionCreator.java | 38 ++++- .../ElasticInferenceServiceActionVisitor.java | 3 + .../ElasticInferenceServiceRerankModel.java | 104 ++++++++++++ ...InferenceServiceRerankServiceSettings.java | 126 +++++++++++++++ ...erenceServiceRerankRequestEntityTests.java | 122 +++++++++++++++ ...ticInferenceServiceRerankRequestTests.java | 89 +++++++++++ ...renceServiceRerankResponseEntityTests.java | 148 ++++++++++++++++++ .../elastic/ElasticInferenceServiceTests.java | 139 +++++++++++++++- ...ticInferenceServiceActionCreatorTests.java | 75 +++++++++ ...asticInferenceServiceRerankModelTests.java | 30 ++++ ...enceServiceRerankServiceSettingsTests.java | 76 +++++++++ 20 files changed, 1232 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequest.java create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequestEntity.java create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntity.java create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModel.java create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettings.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestEntityTests.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestTests.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntityTests.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModelTests.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettingsTests.java diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 938977c1221a6..669952c6026e7 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -193,6 +193,7 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_QUERY_PLANNING_DURATION_8_19 = def(8_841_0_45); public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM_8_19 = def(8_841_0_46); public static final TransportVersion ML_INFERENCE_MISTRAL_CHAT_COMPLETION_ADDED_8_19 = def(8_841_0_47); + public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK_ADDED_8_19 = def(8_841_0_48); public static final TransportVersion V_9_0_0 = def(9_000_0_09); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_1 = def(9_000_0_10); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_2 = def(9_000_0_11); @@ -290,6 +291,7 @@ static TransportVersion def(int id) { public static final TransportVersion IDP_CUSTOM_SAML_ATTRIBUTES_ALLOW_LIST = def(9_091_0_00); public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM = def(9_092_0_00); public static final TransportVersion SNAPSHOT_INDEX_SHARD_STATUS_MISSING_STATS = def(9_093_0_00); + public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK = def(9_094_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java index 54e8f3102aa45..3fd5b06450a73 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java @@ -70,6 +70,7 @@ import org.elasticsearch.xpack.inference.services.custom.response.TextEmbeddingResponseParser; import org.elasticsearch.xpack.inference.services.deepseek.DeepSeekChatCompletionModel; import org.elasticsearch.xpack.inference.services.elastic.completion.ElasticInferenceServiceCompletionServiceSettings; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankServiceSettings; import org.elasticsearch.xpack.inference.services.elastic.sparseembeddings.ElasticInferenceServiceSparseEmbeddingsServiceSettings; import org.elasticsearch.xpack.inference.services.elasticsearch.CustomElandInternalServiceSettings; import org.elasticsearch.xpack.inference.services.elasticsearch.CustomElandInternalTextEmbeddingServiceSettings; @@ -166,7 +167,7 @@ public static List getNamedWriteables() { addAnthropicNamedWritables(namedWriteables); addAmazonBedrockNamedWriteables(namedWriteables); addAwsNamedWriteables(namedWriteables); - addEisNamedWriteables(namedWriteables); + addElasticNamedWriteables(namedWriteables); addAlibabaCloudSearchNamedWriteables(namedWriteables); addJinaAINamedWriteables(namedWriteables); addVoyageAINamedWriteables(namedWriteables); @@ -742,7 +743,8 @@ private static void addVoyageAINamedWriteables(List namedWriteables) { + private static void addElasticNamedWriteables(List namedWriteables) { + // Sparse Text Embeddings namedWriteables.add( new NamedWriteableRegistry.Entry( ServiceSettings.class, @@ -750,6 +752,8 @@ private static void addEisNamedWriteables(List nam ElasticInferenceServiceSparseEmbeddingsServiceSettings::new ) ); + + // Completion namedWriteables.add( new NamedWriteableRegistry.Entry( ServiceSettings.class, @@ -757,5 +761,14 @@ private static void addEisNamedWriteables(List nam ElasticInferenceServiceCompletionServiceSettings::new ) ); + + // Rerank + namedWriteables.add( + new NamedWriteableRegistry.Entry( + ServiceSettings.class, + ElasticInferenceServiceRerankServiceSettings.NAME, + ElasticInferenceServiceRerankServiceSettings::new + ) + ); } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequest.java new file mode 100644 index 0000000000000..08b3fd2384642 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequest.java @@ -0,0 +1,94 @@ +/* + * 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.inference.external.request.elastic.rerank; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.message.BasicHeader; +import org.elasticsearch.common.Strings; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.services.elastic.request.ElasticInferenceServiceRequest; +import org.elasticsearch.xpack.inference.services.elastic.request.ElasticInferenceServiceRequestMetadata; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModel; +import org.elasticsearch.xpack.inference.telemetry.TraceContext; +import org.elasticsearch.xpack.inference.telemetry.TraceContextHandler; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +public class ElasticInferenceServiceRerankRequest extends ElasticInferenceServiceRequest { + + private final String query; + private final List documents; + private final Integer topN; + private final TraceContextHandler traceContextHandler; + private final ElasticInferenceServiceRerankModel model; + + public ElasticInferenceServiceRerankRequest( + String query, + List documents, + Integer topN, + ElasticInferenceServiceRerankModel model, + TraceContext traceContext, + ElasticInferenceServiceRequestMetadata metadata + ) { + super(metadata); + this.query = query; + this.documents = documents; + this.topN = topN; + this.model = Objects.requireNonNull(model); + this.traceContextHandler = new TraceContextHandler(traceContext); + } + + @Override + public HttpRequestBase createHttpRequestBase() { + var httpPost = new HttpPost(getURI()); + var requestEntity = Strings.toString( + new ElasticInferenceServiceRerankRequestEntity(query, documents, model.getServiceSettings().modelId(), topN) + ); + + ByteArrayEntity byteEntity = new ByteArrayEntity(requestEntity.getBytes(StandardCharsets.UTF_8)); + httpPost.setEntity(byteEntity); + + traceContextHandler.propagateTraceContext(httpPost); + httpPost.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType())); + + return httpPost; + } + + public TraceContext getTraceContext() { + return traceContextHandler.traceContext(); + } + + @Override + public String getInferenceEntityId() { + return model.getInferenceEntityId(); + } + + @Override + public URI getURI() { + return model.uri(); + } + + @Override + public Request truncate() { + // no truncation + return this; + } + + @Override + public boolean[] getTruncationInfo() { + // no truncation + return null; + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequestEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequestEntity.java new file mode 100644 index 0000000000000..b542af93047fa --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/elastic/rerank/ElasticInferenceServiceRerankRequestEntity.java @@ -0,0 +1,59 @@ +/* + * 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.inference.external.request.elastic.rerank; + +import org.elasticsearch.core.Nullable; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +public record ElasticInferenceServiceRerankRequestEntity( + String query, + List documents, + String modelId, + @Nullable Integer topNDocumentsOnly +) implements ToXContentObject { + + private static final String QUERY_FIELD = "query"; + private static final String MODEL_FIELD = "model"; + private static final String TOP_N_DOCUMENTS_ONLY_FIELD = "top_n"; + private static final String DOCUMENTS_FIELD = "documents"; + + public ElasticInferenceServiceRerankRequestEntity { + Objects.requireNonNull(query); + Objects.requireNonNull(documents); + Objects.requireNonNull(modelId); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + builder.field(QUERY_FIELD, query); + + builder.field(MODEL_FIELD, modelId); + + if (Objects.nonNull(topNDocumentsOnly)) { + builder.field(TOP_N_DOCUMENTS_ONLY_FIELD, topNDocumentsOnly); + } + + builder.startArray(DOCUMENTS_FIELD); + for (String document : documents) { + builder.value(document); + } + + builder.endArray(); + + builder.endObject(); + + return builder; + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntity.java new file mode 100644 index 0000000000000..b226e82ae7d91 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntity.java @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.response.elastic; + +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.inference.results.RankedDocsResults; +import org.elasticsearch.xpack.inference.external.http.HttpResult; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; + +public class ElasticInferenceServiceRerankResponseEntity { + + record RerankResult(List entries) { + + @SuppressWarnings("unchecked") + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + RerankResult.class.getSimpleName(), + true, + args -> new RerankResult((List) args[0]) + ); + + static { + PARSER.declareObjectArray(constructorArg(), RerankResultEntry.PARSER::apply, new ParseField("results")); + } + + record RerankResultEntry(Integer index, Float relevanceScore) { + + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + RerankResultEntry.class.getSimpleName(), + args -> new RerankResultEntry((Integer) args[0], (Float) args[1]) + ); + + static { + PARSER.declareInt(constructorArg(), new ParseField("index")); + PARSER.declareFloat(constructorArg(), new ParseField("relevance_score")); + } + + public RankedDocsResults.RankedDoc toRankedDoc() { + return new RankedDocsResults.RankedDoc(index, relevanceScore, null); + } + } + } + + public static InferenceServiceResults fromResponse(HttpResult response) throws IOException { + var parserConfig = XContentParserConfiguration.EMPTY.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE); + + try (XContentParser jsonParser = XContentFactory.xContent(XContentType.JSON).createParser(parserConfig, response.body())) { + var rerankResult = RerankResult.PARSER.apply(jsonParser, null); + + return new RankedDocsResults(rerankResult.entries.stream().map(RerankResult.RerankResultEntry::toRankedDoc).toList()); + } + } + + private ElasticInferenceServiceRerankResponseEntity() {} +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceService.java index dcaafb702eba3..d3ef7c97489fc 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceService.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceService.java @@ -51,6 +51,7 @@ import org.elasticsearch.xpack.inference.services.elastic.authorization.ElasticInferenceServiceAuthorizationRequestHandler; import org.elasticsearch.xpack.inference.services.elastic.completion.ElasticInferenceServiceCompletionModel; import org.elasticsearch.xpack.inference.services.elastic.completion.ElasticInferenceServiceCompletionServiceSettings; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModel; import org.elasticsearch.xpack.inference.services.elastic.sparseembeddings.ElasticInferenceServiceSparseEmbeddingsModel; import org.elasticsearch.xpack.inference.services.elastic.sparseembeddings.ElasticInferenceServiceSparseEmbeddingsServiceSettings; import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; @@ -79,7 +80,11 @@ public class ElasticInferenceService extends SenderService { public static final String NAME = "elastic"; public static final String ELASTIC_INFERENCE_SERVICE_IDENTIFIER = "Elastic Inference Service"; - private static final EnumSet IMPLEMENTED_TASK_TYPES = EnumSet.of(TaskType.SPARSE_EMBEDDING, TaskType.CHAT_COMPLETION); + private static final EnumSet IMPLEMENTED_TASK_TYPES = EnumSet.of( + TaskType.SPARSE_EMBEDDING, + TaskType.CHAT_COMPLETION, + TaskType.RERANK + ); private static final String SERVICE_NAME = "Elastic"; // rainbow-sprinkles @@ -93,7 +98,7 @@ public class ElasticInferenceService extends SenderService { /** * The task types that the {@link InferenceAction.Request} can accept. */ - private static final EnumSet SUPPORTED_INFERENCE_ACTION_TASK_TYPES = EnumSet.of(TaskType.SPARSE_EMBEDDING); + private static final EnumSet SUPPORTED_INFERENCE_ACTION_TASK_TYPES = EnumSet.of(TaskType.SPARSE_EMBEDDING, TaskType.RERANK); public static String defaultEndpointId(String modelId) { return Strings.format(".%s-elastic", modelId); @@ -163,6 +168,18 @@ public void onNodeStarted() { authorizationHandler.init(); } + @Override + protected void validateRerankParameters(Boolean returnDocuments, Integer topN, ValidationException validationException) { + if (returnDocuments != null) { + validationException.addValidationError( + org.elasticsearch.core.Strings.format( + "Invalid return_documents [%s]. The return_documents option is not supported by this service", + returnDocuments + ) + ); + } + } + /** * Only use this in tests. * @@ -335,7 +352,7 @@ private static ElasticInferenceServiceModel createModel( Map serviceSettings, Map taskSettings, @Nullable Map secretSettings, - ElasticInferenceServiceComponents eisServiceComponents, + ElasticInferenceServiceComponents elasticInferenceServiceComponents, String failureMessage, ConfigurationParseContext context ) { @@ -347,7 +364,7 @@ private static ElasticInferenceServiceModel createModel( serviceSettings, taskSettings, secretSettings, - eisServiceComponents, + elasticInferenceServiceComponents, context ); case CHAT_COMPLETION -> new ElasticInferenceServiceCompletionModel( @@ -357,7 +374,17 @@ private static ElasticInferenceServiceModel createModel( serviceSettings, taskSettings, secretSettings, - eisServiceComponents, + elasticInferenceServiceComponents, + context + ); + case RERANK -> new ElasticInferenceServiceRerankModel( + inferenceEntityId, + taskType, + NAME, + serviceSettings, + taskSettings, + secretSettings, + elasticInferenceServiceComponents, context ); default -> throw new ElasticsearchStatusException(failureMessage, RestStatus.BAD_REQUEST); @@ -462,9 +489,8 @@ private LazyInitializable initC configurationMap.put( MODEL_ID, - new SettingsConfiguration.Builder(EnumSet.of(TaskType.SPARSE_EMBEDDING, TaskType.CHAT_COMPLETION)).setDescription( - "The name of the model to use for the inference task." - ) + new SettingsConfiguration.Builder(EnumSet.of(TaskType.SPARSE_EMBEDDING, TaskType.CHAT_COMPLETION, TaskType.RERANK)) + .setDescription("The name of the model to use for the inference task.") .setLabel("Model ID") .setRequired(true) .setSensitive(false) @@ -487,7 +513,9 @@ private LazyInitializable initC ); configurationMap.putAll( - RateLimitSettings.toSettingsConfiguration(EnumSet.of(TaskType.SPARSE_EMBEDDING, TaskType.CHAT_COMPLETION)) + RateLimitSettings.toSettingsConfiguration( + EnumSet.of(TaskType.SPARSE_EMBEDDING, TaskType.CHAT_COMPLETION, TaskType.RERANK) + ) ); return new InferenceServiceConfiguration.Builder().setService(NAME) diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceModel.java index e03cc36e62417..34a8086119150 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceModel.java @@ -7,14 +7,15 @@ package org.elasticsearch.xpack.inference.services.elastic; -import org.elasticsearch.inference.Model; import org.elasticsearch.inference.ModelConfigurations; import org.elasticsearch.inference.ModelSecrets; import org.elasticsearch.inference.ServiceSettings; +import org.elasticsearch.xpack.inference.services.RateLimitGroupingModel; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; import java.util.Objects; -public abstract class ElasticInferenceServiceModel extends Model { +public abstract class ElasticInferenceServiceModel extends RateLimitGroupingModel { private final ElasticInferenceServiceRateLimitServiceSettings rateLimitServiceSettings; @@ -35,12 +36,18 @@ public ElasticInferenceServiceModel( public ElasticInferenceServiceModel(ElasticInferenceServiceModel model, ServiceSettings serviceSettings) { super(model, serviceSettings); - this.rateLimitServiceSettings = model.rateLimitServiceSettings(); + this.rateLimitServiceSettings = model.rateLimitServiceSettings; this.elasticInferenceServiceComponents = model.elasticInferenceServiceComponents(); } - public ElasticInferenceServiceRateLimitServiceSettings rateLimitServiceSettings() { - return rateLimitServiceSettings; + @Override + public int rateLimitGroupingHash() { + // We only have one model for rerank + return Objects.hash(this.getServiceSettings().modelId()); + } + + public RateLimitSettings rateLimitSettings() { + return rateLimitServiceSettings.rateLimitSettings(); } public ElasticInferenceServiceComponents elasticInferenceServiceComponents() { diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceRequestManager.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceRequestManager.java index 1c4b6cb340ecc..8d5556e64f3b8 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceRequestManager.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceRequestManager.java @@ -20,7 +20,7 @@ public abstract class ElasticInferenceServiceRequestManager extends BaseRequestM private final ElasticInferenceServiceRequestMetadata requestMetadata; protected ElasticInferenceServiceRequestManager(ThreadPool threadPool, ElasticInferenceServiceModel model) { - super(threadPool, model.getInferenceEntityId(), RateLimitGrouping.of(model), model.rateLimitServiceSettings().rateLimitSettings()); + super(threadPool, model.getInferenceEntityId(), RateLimitGrouping.of(model), model.rateLimitSettings()); this.requestMetadata = extractRequestMetadataFromThreadContext(threadPool.getThreadContext()); } @@ -32,7 +32,7 @@ record RateLimitGrouping(int modelIdHash) { public static RateLimitGrouping of(ElasticInferenceServiceModel model) { Objects.requireNonNull(model); - return new RateLimitGrouping(model.rateLimitServiceSettings().modelId().hashCode()); + return new RateLimitGrouping(model.rateLimitGroupingHash()); } } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsRequestManager.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsRequestManager.java index 93a601b3c3489..ea82eb228dbc8 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsRequestManager.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsRequestManager.java @@ -31,6 +31,7 @@ import static org.elasticsearch.xpack.inference.common.Truncator.truncate; import static org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceService.ELASTIC_INFERENCE_SERVICE_IDENTIFIER; +// TODO: remove and use GenericRequestManager in ElasticInferenceServiceActionCreator public class ElasticInferenceServiceSparseEmbeddingsRequestManager extends ElasticInferenceServiceRequestManager { private static final Logger logger = LogManager.getLogger(ElasticInferenceServiceSparseEmbeddingsRequestManager.class); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreator.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreator.java index 5bdae8582f371..c3b4062660199 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreator.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreator.java @@ -7,19 +7,27 @@ package org.elasticsearch.xpack.inference.services.elastic.action; +import org.elasticsearch.common.Strings; import org.elasticsearch.xpack.inference.external.action.ExecutableAction; import org.elasticsearch.xpack.inference.external.action.SenderExecutableAction; +import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler; +import org.elasticsearch.xpack.inference.external.http.sender.GenericRequestManager; +import org.elasticsearch.xpack.inference.external.http.sender.QueryAndDocsInputs; import org.elasticsearch.xpack.inference.external.http.sender.Sender; +import org.elasticsearch.xpack.inference.external.request.elastic.rerank.ElasticInferenceServiceRerankRequest; +import org.elasticsearch.xpack.inference.external.response.elastic.ElasticInferenceServiceRerankResponseEntity; import org.elasticsearch.xpack.inference.services.ServiceComponents; +import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceResponseHandler; import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceSparseEmbeddingsRequestManager; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModel; import org.elasticsearch.xpack.inference.services.elastic.sparseembeddings.ElasticInferenceServiceSparseEmbeddingsModel; import org.elasticsearch.xpack.inference.telemetry.TraceContext; -import java.util.Locale; import java.util.Objects; import static org.elasticsearch.xpack.inference.external.action.ActionUtils.constructFailedToSendRequestMessage; import static org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceService.ELASTIC_INFERENCE_SERVICE_IDENTIFIER; +import static org.elasticsearch.xpack.inference.services.elastic.request.ElasticInferenceServiceRequest.extractRequestMetadataFromThreadContext; public class ElasticInferenceServiceActionCreator implements ElasticInferenceServiceActionVisitor { @@ -29,6 +37,11 @@ public class ElasticInferenceServiceActionCreator implements ElasticInferenceSer private final TraceContext traceContext; + static final ResponseHandler RERANK_HANDLER = new ElasticInferenceServiceResponseHandler( + "elastic rerank", + (request, response) -> ElasticInferenceServiceRerankResponseEntity.fromResponse(response) + ); + public ElasticInferenceServiceActionCreator(Sender sender, ServiceComponents serviceComponents, TraceContext traceContext) { this.sender = Objects.requireNonNull(sender); this.serviceComponents = Objects.requireNonNull(serviceComponents); @@ -39,8 +52,29 @@ public ElasticInferenceServiceActionCreator(Sender sender, ServiceComponents ser public ExecutableAction create(ElasticInferenceServiceSparseEmbeddingsModel model) { var requestManager = new ElasticInferenceServiceSparseEmbeddingsRequestManager(model, serviceComponents, traceContext); var errorMessage = constructFailedToSendRequestMessage( - String.format(Locale.ROOT, "%s sparse embeddings", ELASTIC_INFERENCE_SERVICE_IDENTIFIER) + Strings.format("%s sparse embeddings", ELASTIC_INFERENCE_SERVICE_IDENTIFIER) + ); + return new SenderExecutableAction(sender, requestManager, errorMessage); + } + + @Override + public ExecutableAction create(ElasticInferenceServiceRerankModel model) { + var threadPool = serviceComponents.threadPool(); + var requestManager = new GenericRequestManager<>( + threadPool, + model, + RERANK_HANDLER, + (rerankInput) -> new ElasticInferenceServiceRerankRequest( + rerankInput.getQuery(), + rerankInput.getChunks(), + rerankInput.getTopN(), + model, + traceContext, + extractRequestMetadataFromThreadContext(threadPool.getThreadContext()) + ), + QueryAndDocsInputs.class ); + var errorMessage = constructFailedToSendRequestMessage(Strings.format("%s rerank", ELASTIC_INFERENCE_SERVICE_IDENTIFIER)); return new SenderExecutableAction(sender, requestManager, errorMessage); } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionVisitor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionVisitor.java index 2fdd90b8169bd..2bfb6d9f12223 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionVisitor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionVisitor.java @@ -8,10 +8,13 @@ package org.elasticsearch.xpack.inference.services.elastic.action; import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModel; import org.elasticsearch.xpack.inference.services.elastic.sparseembeddings.ElasticInferenceServiceSparseEmbeddingsModel; public interface ElasticInferenceServiceActionVisitor { ExecutableAction create(ElasticInferenceServiceSparseEmbeddingsModel model); + ExecutableAction create(ElasticInferenceServiceRerankModel model); + } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModel.java new file mode 100644 index 0000000000000..7e592406a718a --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModel.java @@ -0,0 +1,104 @@ +/* + * 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.inference.services.elastic.rerank; + +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.EmptySecretSettings; +import org.elasticsearch.inference.EmptyTaskSettings; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.SecretSettings; +import org.elasticsearch.inference.TaskSettings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceComponents; +import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceExecutableActionModel; +import org.elasticsearch.xpack.inference.services.elastic.action.ElasticInferenceServiceActionVisitor; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +public class ElasticInferenceServiceRerankModel extends ElasticInferenceServiceExecutableActionModel { + + private final URI uri; + + public ElasticInferenceServiceRerankModel( + String inferenceEntityId, + TaskType taskType, + String service, + Map serviceSettings, + Map taskSettings, + Map secrets, + ElasticInferenceServiceComponents elasticInferenceServiceComponents, + ConfigurationParseContext context + ) { + this( + inferenceEntityId, + taskType, + service, + ElasticInferenceServiceRerankServiceSettings.fromMap(serviceSettings, context), + EmptyTaskSettings.INSTANCE, + EmptySecretSettings.INSTANCE, + elasticInferenceServiceComponents + ); + } + + public ElasticInferenceServiceRerankModel( + String inferenceEntityId, + TaskType taskType, + String service, + ElasticInferenceServiceRerankServiceSettings serviceSettings, + @Nullable TaskSettings taskSettings, + @Nullable SecretSettings secretSettings, + ElasticInferenceServiceComponents elasticInferenceServiceComponents + ) { + super( + new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, taskSettings), + new ModelSecrets(secretSettings), + serviceSettings, + elasticInferenceServiceComponents + ); + this.uri = createUri(); + } + + @Override + public ExecutableAction accept(ElasticInferenceServiceActionVisitor visitor, Map taskSettings) { + return visitor.create(this); + } + + @Override + public ElasticInferenceServiceRerankServiceSettings getServiceSettings() { + return (ElasticInferenceServiceRerankServiceSettings) super.getServiceSettings(); + } + + public URI uri() { + return uri; + } + + private URI createUri() throws ElasticsearchStatusException { + try { + // TODO, consider transforming the base URL into a URI for better error handling. + return new URI(elasticInferenceServiceComponents().elasticInferenceServiceUrl() + "/api/v1/rerank"); + } catch (URISyntaxException e) { + throw new ElasticsearchStatusException( + "Failed to create URI for service [" + + this.getConfigurations().getService() + + "] with taskType [" + + this.getTaskType() + + "]: " + + e.getMessage(), + RestStatus.BAD_REQUEST, + e + ); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettings.java new file mode 100644 index 0000000000000..c20846c7fdfc2 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettings.java @@ -0,0 +1,126 @@ +/* + * 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.inference.services.elastic.rerank; + +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ServiceSettings; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceService; +import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceRateLimitServiceSettings; +import org.elasticsearch.xpack.inference.services.settings.FilteredXContentObject; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.services.ServiceFields.MODEL_ID; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractRequiredString; + +public class ElasticInferenceServiceRerankServiceSettings extends FilteredXContentObject + implements + ServiceSettings, + ElasticInferenceServiceRateLimitServiceSettings { + + public static final String NAME = "elastic_rerank_service_settings"; + + private static final RateLimitSettings DEFAULT_RATE_LIMIT_SETTINGS = new RateLimitSettings(500); + + public static ElasticInferenceServiceRerankServiceSettings fromMap(Map map, ConfigurationParseContext context) { + ValidationException validationException = new ValidationException(); + + String modelId = extractRequiredString(map, MODEL_ID, ModelConfigurations.SERVICE_SETTINGS, validationException); + RateLimitSettings rateLimitSettings = RateLimitSettings.of( + map, + DEFAULT_RATE_LIMIT_SETTINGS, + validationException, + ElasticInferenceService.NAME, + context + ); + + return new ElasticInferenceServiceRerankServiceSettings(modelId, rateLimitSettings); + } + + private final String modelId; + + private final RateLimitSettings rateLimitSettings; + + public ElasticInferenceServiceRerankServiceSettings(String modelId, RateLimitSettings rateLimitSettings) { + this.modelId = Objects.requireNonNull(modelId); + this.rateLimitSettings = Objects.requireNonNullElse(rateLimitSettings, DEFAULT_RATE_LIMIT_SETTINGS); + } + + public ElasticInferenceServiceRerankServiceSettings(StreamInput in) throws IOException { + this.modelId = in.readString(); + this.rateLimitSettings = new RateLimitSettings(in); + } + + @Override + public String modelId() { + return modelId; + } + + @Override + public RateLimitSettings rateLimitSettings() { + return rateLimitSettings; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_ELASTIC_RERANK; + } + + @Override + protected XContentBuilder toXContentFragmentOfExposedFields(XContentBuilder builder, Params params) throws IOException { + builder.field(MODEL_ID, modelId); + rateLimitSettings.toXContent(builder, params); + + return builder; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + toXContentFragmentOfExposedFields(builder, params); + + builder.endObject(); + + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(modelId); + rateLimitSettings.writeTo(out); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ElasticInferenceServiceRerankServiceSettings that = (ElasticInferenceServiceRerankServiceSettings) o; + return Objects.equals(modelId, that.modelId) && Objects.equals(rateLimitSettings, that.rateLimitSettings); + } + + @Override + public int hashCode() { + return Objects.hash(modelId, rateLimitSettings); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestEntityTests.java new file mode 100644 index 0000000000000..407d3e38b4da1 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestEntityTests.java @@ -0,0 +1,122 @@ +/* + * 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.inference.external.request.elastic; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.request.elastic.rerank.ElasticInferenceServiceRerankRequestEntity; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.xpack.inference.MatchersUtils.equalToIgnoringWhitespaceInJsonString; + +public class ElasticInferenceServiceRerankRequestEntityTests extends ESTestCase { + + public void testToXContent_SingleDocument_NoTopN() throws IOException { + var entity = new ElasticInferenceServiceRerankRequestEntity("query", List.of("document 1"), "rerank-model-id", null); + String xContentString = xContentEntityToString(entity); + assertThat(xContentString, equalToIgnoringWhitespaceInJsonString(""" + { + "query": "query", + "model": "rerank-model-id", + "documents": ["document 1"] + }""")); + } + + public void testToXContent_MultipleDocuments_NoTopN() throws IOException { + var entity = new ElasticInferenceServiceRerankRequestEntity( + "query", + List.of("document 1", "document 2", "document 3"), + "rerank-model-id", + null + ); + String xContentString = xContentEntityToString(entity); + assertThat(xContentString, equalToIgnoringWhitespaceInJsonString(""" + { + "query": "query", + "model": "rerank-model-id", + "documents": [ + "document 1", + "document 2", + "document 3" + ] + } + """)); + } + + public void testToXContent_SingleDocument_WithTopN() throws IOException { + var entity = new ElasticInferenceServiceRerankRequestEntity("query", List.of("document 1"), "rerank-model-id", 3); + String xContentString = xContentEntityToString(entity); + assertThat(xContentString, equalToIgnoringWhitespaceInJsonString(""" + { + "query": "query", + "model": "rerank-model-id", + "top_n": 3, + "documents": ["document 1"] + } + """)); + } + + public void testToXContent_MultipleDocuments_WithTopN() throws IOException { + var entity = new ElasticInferenceServiceRerankRequestEntity( + "query", + List.of("document 1", "document 2", "document 3", "document 4", "document 5"), + "rerank-model-id", + 3 + ); + String xContentString = xContentEntityToString(entity); + assertThat(xContentString, equalToIgnoringWhitespaceInJsonString(""" + { + "query": "query", + "model": "rerank-model-id", + "top_n": 3, + "documents": [ + "document 1", + "document 2", + "document 3", + "document 4", + "document 5" + ] + } + """)); + } + + public void testNullQueryThrowsException() { + NullPointerException e = expectThrows( + NullPointerException.class, + () -> new ElasticInferenceServiceRerankRequestEntity(null, List.of("document 1"), "model-id", null) + ); + assertNotNull(e); + } + + public void testNullDocumentsThrowsException() { + NullPointerException e = expectThrows( + NullPointerException.class, + () -> new ElasticInferenceServiceRerankRequestEntity("query", null, "model-id", null) + ); + assertNotNull(e); + } + + public void testNullModelIdThrowsException() { + NullPointerException e = expectThrows( + NullPointerException.class, + () -> new ElasticInferenceServiceRerankRequestEntity("query", List.of("document 1"), null, null) + ); + assertNotNull(e); + } + + private String xContentEntityToString(ElasticInferenceServiceRerankRequestEntity entity) throws IOException { + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + return Strings.toString(builder); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestTests.java new file mode 100644 index 0000000000000..4e6efed6faa59 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/elastic/ElasticInferenceServiceRerankRequestTests.java @@ -0,0 +1,89 @@ +/* + * 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.inference.external.request.elastic; + +import org.apache.http.client.methods.HttpPost; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.external.request.elastic.rerank.ElasticInferenceServiceRerankRequest; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModelTests; +import org.elasticsearch.xpack.inference.telemetry.TraceContext; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.elasticsearch.xpack.inference.services.elastic.request.ElasticInferenceServiceRequestTests.randomElasticInferenceServiceRequestMetadata; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class ElasticInferenceServiceRerankRequestTests extends ESTestCase { + + public void testTraceContextPropagatedThroughHTTPHeaders() { + var url = "http://eis-gateway.com"; + var query = "query"; + var documents = List.of("document 1", "document 2", "document 3"); + var modelId = "my-model-id"; + var topN = 3; + + var request = createRequest(url, modelId, query, documents, topN); + var httpRequest = request.createHttpRequest(); + + assertThat(httpRequest.httpRequestBase(), instanceOf(HttpPost.class)); + var httpPost = (HttpPost) httpRequest.httpRequestBase(); + + var traceParent = request.getTraceContext().traceParent(); + var traceState = request.getTraceContext().traceState(); + + assertThat(httpPost.getLastHeader(Task.TRACE_PARENT_HTTP_HEADER).getValue(), is(traceParent)); + assertThat(httpPost.getLastHeader(Task.TRACE_STATE).getValue(), is(traceState)); + } + + public void testTruncate_DoesNotTruncate() throws IOException { + var url = "http://eis-gateway.com"; + var query = "query"; + var documents = List.of("document 1", "document 2", "document 3"); + var modelId = "my-model-id"; + var topN = 3; + + var request = createRequest(url, modelId, query, documents, topN); + var truncatedRequest = request.truncate(); + + var httpRequest = truncatedRequest.createHttpRequest(); + assertThat(httpRequest.httpRequestBase(), instanceOf(HttpPost.class)); + + var httpPost = (HttpPost) httpRequest.httpRequestBase(); + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(4)); + assertThat(requestMap.get("query"), is(query)); + assertThat(requestMap.get("model"), is(modelId)); + assertThat(requestMap.get("documents"), is(documents)); + assertThat(requestMap.get("top_n"), is(topN)); + } + + private ElasticInferenceServiceRerankRequest createRequest( + String url, + String modelId, + String query, + List documents, + Integer topN + ) { + var rerankModel = ElasticInferenceServiceRerankModelTests.createModel(url, modelId); + + return new ElasticInferenceServiceRerankRequest( + query, + documents, + topN, + rerankModel, + new TraceContext(randomAlphaOfLength(10), randomAlphaOfLength(10)), + randomElasticInferenceServiceRequestMetadata() + ); + } + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntityTests.java new file mode 100644 index 0000000000000..2d3b9fb309dbb --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/elastic/ElasticInferenceServiceRerankResponseEntityTests.java @@ -0,0 +1,148 @@ +/* + * 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.inference.external.response.elastic; + +import org.apache.http.HttpResponse; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.inference.results.RankedDocsResults; +import org.elasticsearch.xpack.inference.external.http.HttpResult; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class ElasticInferenceServiceRerankResponseEntityTests extends ESTestCase { + + public void testFromResponse_CreatesResultsForASingleItem() throws IOException { + String responseJson = """ + { + "results": [ + { + "index": 0, + "relevance_score": 0.94 + } + ] + } + """; + + RankedDocsResults parsedResults = (RankedDocsResults) ElasticInferenceServiceRerankResponseEntity.fromResponse( + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(parsedResults.getRankedDocs(), is(List.of(new RankedDocsResults.RankedDoc(0, 0.94F, null)))); + } + + public void testFromResponse_CreatesResultsForMultipleItems() throws IOException { + String responseJson = """ + { + "results": [ + { + "index": 0, + "relevance_score": 0.94 + }, + { + "index": 1, + "relevance_score": 0.78 + }, + { + "index": 2, + "relevance_score": 0.65 + } + ] + } + """; + + RankedDocsResults parsedResults = (RankedDocsResults) ElasticInferenceServiceRerankResponseEntity.fromResponse( + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat( + parsedResults.getRankedDocs(), + is( + List.of( + new RankedDocsResults.RankedDoc(0, 0.94F, null), + new RankedDocsResults.RankedDoc(1, 0.78F, null), + new RankedDocsResults.RankedDoc(2, 0.65F, null) + ) + ) + ); + } + + public void testFromResponse_HandlesFloatingPointPrecision() throws IOException { + String responseJson = """ + { + "results": [ + { + "index": 0, + "relevance_score": 0.9432156 + } + ] + } + """; + + RankedDocsResults parsedResults = (RankedDocsResults) ElasticInferenceServiceRerankResponseEntity.fromResponse( + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(parsedResults.getRankedDocs(), is(List.of(new RankedDocsResults.RankedDoc(0, 0.9432156F, null)))); + } + + public void testFromResponse_OrderIsPreserved() throws IOException { + String responseJson = """ + { + "results": [ + { + "index": 2, + "relevance_score": 0.94 + }, + { + "index": 0, + "relevance_score": 0.78 + }, + { + "index": 1, + "relevance_score": 0.65 + } + ] + } + """; + + RankedDocsResults parsedResults = (RankedDocsResults) ElasticInferenceServiceRerankResponseEntity.fromResponse( + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + // Verify the order is maintained from the response + assertThat( + parsedResults.getRankedDocs(), + is( + List.of( + new RankedDocsResults.RankedDoc(2, 0.94F, null), + new RankedDocsResults.RankedDoc(0, 0.78F, null), + new RankedDocsResults.RankedDoc(1, 0.65F, null) + ) + ) + ); + } + + public void testFromResponse_HandlesEmptyResultsList() throws IOException { + String responseJson = """ + { + "results": [] + } + """; + + RankedDocsResults parsedResults = (RankedDocsResults) ElasticInferenceServiceRerankResponseEntity.fromResponse( + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(parsedResults.getRankedDocs(), is(List.of())); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceTests.java index 3dce20004f626..b3dab72c1410d 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Settings; @@ -58,6 +59,8 @@ import org.elasticsearch.xpack.inference.services.elastic.authorization.ElasticInferenceServiceAuthorizationRequestHandler; import org.elasticsearch.xpack.inference.services.elastic.completion.ElasticInferenceServiceCompletionModel; import org.elasticsearch.xpack.inference.services.elastic.completion.ElasticInferenceServiceCompletionServiceSettings; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModel; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModelTests; import org.elasticsearch.xpack.inference.services.elastic.response.ElasticInferenceServiceAuthorizationResponseEntity; import org.elasticsearch.xpack.inference.services.elastic.sparseembeddings.ElasticInferenceServiceSparseEmbeddingsModel; import org.elasticsearch.xpack.inference.services.elasticsearch.ElserModels; @@ -150,6 +153,23 @@ public void testParseRequestConfig_CreatesASparseEmbeddingsModel() throws IOExce } } + public void testParseRequestConfig_CreatesARerankModel() throws IOException { + try (var service = createServiceWithMockSender()) { + ActionListener modelListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(ElasticInferenceServiceRerankModel.class)); + ElasticInferenceServiceRerankModel rerankModel = (ElasticInferenceServiceRerankModel) model; + assertThat(rerankModel.getServiceSettings().modelId(), is("my-rerank-model-id")); + }, e -> fail("Model parsing should have succeeded, but failed: " + e.getMessage())); + + service.parseRequestConfig( + "id", + TaskType.RERANK, + getRequestConfigMap(Map.of(ServiceFields.MODEL_ID, "my-rerank-model-id"), Map.of(), Map.of()), + modelListener + ); + } + } + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInConfig() throws IOException { try (var service = createServiceWithMockSender()) { var config = getRequestConfigMap(Map.of(ServiceFields.MODEL_ID, ElserModels.ELSER_V2_MODEL), Map.of(), Map.of()); @@ -368,6 +388,39 @@ public void testInfer_ThrowsErrorWhenModelIsNotAValidModel() throws IOException verifyNoMoreInteractions(sender); } + public void testInfer_ThrowsValidationErrorForInvalidRerankParams() throws IOException { + var sender = mock(Sender.class); + + var factory = mock(HttpRequestSender.Factory.class); + when(factory.createSender()).thenReturn(sender); + + try (var service = createServiceWithMockSender()) { + var model = ElasticInferenceServiceRerankModelTests.createModel(getUrl(webServer), "my-rerank-model-id"); + PlainActionFuture listener = new PlainActionFuture<>(); + + var thrownException = expectThrows( + ValidationException.class, + () -> service.infer( + model, + "search query", + Boolean.TRUE, + 10, + List.of("doc1", "doc2", "doc3"), + false, + new HashMap<>(), + InputType.SEARCH, + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ) + ); + + assertThat( + thrownException.getMessage(), + is("Validation Failed: 1: Invalid return_documents [true]. The return_documents option is not supported by this service;") + ); + } + } + public void testInfer_ThrowsErrorWhenTaskTypeIsNotValid() throws IOException { var sender = mock(Sender.class); @@ -396,7 +449,7 @@ public void testInfer_ThrowsErrorWhenTaskTypeIsNotValid() throws IOException { thrownException.getMessage(), is( "Inference entity [model_id] does not support task type [text_embedding] " - + "for inference, the task type must be one of [sparse_embedding]." + + "for inference, the task type must be one of [sparse_embedding, rerank]." ) ); @@ -437,7 +490,7 @@ public void testInfer_ThrowsErrorWhenTaskTypeIsNotValid_ChatCompletion() throws thrownException.getMessage(), is( "Inference entity [model_id] does not support task type [chat_completion] " - + "for inference, the task type must be one of [sparse_embedding]. " + + "for inference, the task type must be one of [sparse_embedding, rerank]. " + "The task type for the inference entity is chat_completion, " + "please use the _inference/chat_completion/model_id/_stream URL." ) @@ -505,6 +558,76 @@ public void testInfer_SendsEmbeddingsRequest() throws IOException { } } + @SuppressWarnings("unchecked") + public void testRerank_SendsRerankRequest() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + var elasticInferenceServiceURL = getUrl(webServer); + + try (var service = createService(senderFactory, elasticInferenceServiceURL)) { + var modelId = "my-model-id"; + var topN = 2; + String responseJson = """ + { + "results": [ + {"index": 0, "relevance_score": 0.95}, + {"index": 1, "relevance_score": 0.85}, + {"index": 2, "relevance_score": 0.75} + ] + } + """; + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var model = ElasticInferenceServiceRerankModelTests.createModel(elasticInferenceServiceURL, modelId); + PlainActionFuture listener = new PlainActionFuture<>(); + + service.infer( + model, + "search query", + null, + topN, + List.of("doc1", "doc2", "doc3"), + false, + new HashMap<>(), + InputType.SEARCH, + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ); + var result = listener.actionGet(TIMEOUT); + + var resultMap = result.asMap(); + var rerankResults = (List>) resultMap.get("rerank"); + assertThat(rerankResults.size(), Matchers.is(3)); + + Map rankedDocOne = (Map) rerankResults.get(0).get("ranked_doc"); + Map rankedDocTwo = (Map) rerankResults.get(1).get("ranked_doc"); + Map rankedDocThree = (Map) rerankResults.get(2).get("ranked_doc"); + + assertThat(rankedDocOne.get("index"), equalTo(0)); + assertThat(rankedDocTwo.get("index"), equalTo(1)); + assertThat(rankedDocThree.get("index"), equalTo(2)); + + // Verify the outgoing HTTP request + var request = webServer.requests().getFirst(); + assertNull(request.getUri().getQuery()); + assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE), Matchers.equalTo(XContentType.JSON.mediaType())); + + // Verify the outgoing request body + Map requestMap = entityAsMap(request.getBody()); + Map expectedRequestMap = Map.of( + "query", + "search query", + "model", + modelId, + "top_n", + topN, + "documents", + List.of("doc1", "doc2", "doc3") + ); + assertThat(requestMap, is(expectedRequestMap)); + } + } + public void testInfer_PropagatesProductUseCaseHeader() throws IOException { var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); var elasticInferenceServiceURL = getUrl(webServer); @@ -852,7 +975,7 @@ public void testGetConfiguration() throws Exception { "sensitive": false, "updatable": false, "type": "int", - "supported_task_types": ["sparse_embedding" , "chat_completion"] + "supported_task_types": ["sparse_embedding" , "rerank", "chat_completion"] }, "model_id": { "description": "The name of the model to use for the inference task.", @@ -861,7 +984,7 @@ public void testGetConfiguration() throws Exception { "sensitive": false, "updatable": false, "type": "str", - "supported_task_types": ["sparse_embedding" , "chat_completion"] + "supported_task_types": ["sparse_embedding" , "rerank", "chat_completion"] }, "max_input_tokens": { "description": "Allows you to specify the maximum number of tokens per input.", @@ -907,7 +1030,7 @@ public void testGetConfiguration_WithoutSupportedTaskTypes() throws Exception { "sensitive": false, "updatable": false, "type": "int", - "supported_task_types": ["sparse_embedding" , "chat_completion"] + "supported_task_types": ["sparse_embedding" , "rerank", "chat_completion"] }, "model_id": { "description": "The name of the model to use for the inference task.", @@ -916,7 +1039,7 @@ public void testGetConfiguration_WithoutSupportedTaskTypes() throws Exception { "sensitive": false, "updatable": false, "type": "str", - "supported_task_types": ["sparse_embedding" , "chat_completion"] + "supported_task_types": ["sparse_embedding" , "rerank", "chat_completion"] }, "max_input_tokens": { "description": "Allows you to specify the maximum number of tokens per input.", @@ -976,7 +1099,7 @@ public void testGetConfiguration_WithoutSupportedTaskTypes_WhenModelsReturnTaskO "sensitive": false, "updatable": false, "type": "int", - "supported_task_types": ["sparse_embedding" , "chat_completion"] + "supported_task_types": ["sparse_embedding" , "rerank", "chat_completion"] }, "model_id": { "description": "The name of the model to use for the inference task.", @@ -985,7 +1108,7 @@ public void testGetConfiguration_WithoutSupportedTaskTypes_WhenModelsReturnTaskO "sensitive": false, "updatable": false, "type": "str", - "supported_task_types": ["sparse_embedding" , "chat_completion"] + "supported_task_types": ["sparse_embedding" , "rerank", "chat_completion"] }, "max_input_tokens": { "description": "Allows you to specify the maximum number of tokens per input.", diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreatorTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreatorTests.java index fadf4a899e45d..49957800f3a83 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreatorTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/action/ElasticInferenceServiceActionCreatorTests.java @@ -20,12 +20,15 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.core.inference.action.InferenceAction; +import org.elasticsearch.xpack.core.inference.results.RankedDocsResultsTests; import org.elasticsearch.xpack.core.inference.results.SparseEmbeddingResultsTests; import org.elasticsearch.xpack.inference.external.http.HttpClientManager; import org.elasticsearch.xpack.inference.external.http.sender.EmbeddingsInput; import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests; +import org.elasticsearch.xpack.inference.external.http.sender.QueryAndDocsInputs; import org.elasticsearch.xpack.inference.logging.ThrottlerManager; import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceSparseEmbeddingsModelTests; +import org.elasticsearch.xpack.inference.services.elastic.rerank.ElasticInferenceServiceRerankModelTests; import org.elasticsearch.xpack.inference.telemetry.TraceContext; import org.junit.After; import org.junit.Before; @@ -181,6 +184,78 @@ public void testSend_FailsFromInvalidResponseFormat_ForElserAction() throws IOEx } } + @SuppressWarnings("unchecked") + public void testExecute_ReturnsSuccessfulResponse_ForRerankAction() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var sender = createSender(senderFactory)) { + sender.start(); + + String responseJson = """ + { + "results": [ + { + "index": 0, + "relevance_score": 0.94 + }, + { + "index": 1, + "relevance_score": 0.21 + } + ] + } + """; + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var modelId = "my-model-id"; + var topN = 3; + var query = "query"; + var documents = List.of("document 1", "document 2", "document 3"); + + var model = ElasticInferenceServiceRerankModelTests.createModel(getUrl(webServer), modelId); + var actionCreator = new ElasticInferenceServiceActionCreator(sender, createWithEmptySettings(threadPool), createTraceContext()); + var action = actionCreator.create(model); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new QueryAndDocsInputs(query, documents, null, topN, false), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat( + result.asMap(), + equalTo( + RankedDocsResultsTests.buildExpectationRerank( + List.of( + new RankedDocsResultsTests.RerankExpectation(Map.of("index", 0, "relevance_score", 0.94f)), + new RankedDocsResultsTests.RerankExpectation(Map.of("index", 1, "relevance_score", 0.21f)) + ) + ) + ) + ); + + assertThat(webServer.requests(), hasSize(1)); + assertNull(webServer.requests().get(0).getUri().getQuery()); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.CONTENT_TYPE), equalTo(XContentType.JSON.mediaType())); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + + assertThat(requestMap.size(), is(4)); + + assertThat(requestMap.get("documents"), instanceOf(List.class)); + List requestDocuments = (List) requestMap.get("documents"); + assertThat(requestDocuments.get(0), equalTo(documents.get(0))); + assertThat(requestDocuments.get(1), equalTo(documents.get(1))); + assertThat(requestDocuments.get(2), equalTo(documents.get(2))); + + assertThat(requestMap.get("top_n"), equalTo(topN)); + + assertThat(requestMap.get("query"), equalTo(query)); + + assertThat(requestMap.get("model"), equalTo(modelId)); + } + } + public void testExecute_ReturnsSuccessfulResponse_AfterTruncating() throws IOException { var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModelTests.java new file mode 100644 index 0000000000000..f5da46915e13c --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankModelTests.java @@ -0,0 +1,30 @@ +/* + * 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.inference.services.elastic.rerank; + +import org.elasticsearch.inference.EmptySecretSettings; +import org.elasticsearch.inference.EmptyTaskSettings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.services.elastic.ElasticInferenceServiceComponents; + +public class ElasticInferenceServiceRerankModelTests extends ESTestCase { + + public static ElasticInferenceServiceRerankModel createModel(String url, String modelId) { + return new ElasticInferenceServiceRerankModel( + "id", + TaskType.RERANK, + "service", + new ElasticInferenceServiceRerankServiceSettings(modelId, null), + EmptyTaskSettings.INSTANCE, + EmptySecretSettings.INSTANCE, + ElasticInferenceServiceComponents.of(url) + ); + } + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettingsTests.java new file mode 100644 index 0000000000000..8066da9c43683 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/elastic/rerank/ElasticInferenceServiceRerankServiceSettingsTests.java @@ -0,0 +1,76 @@ +/* + * 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.inference.services.elastic.rerank; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.ServiceFields; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.is; + +public class ElasticInferenceServiceRerankServiceSettingsTests extends AbstractWireSerializingTestCase< + ElasticInferenceServiceRerankServiceSettings> { + + @Override + protected Writeable.Reader instanceReader() { + return ElasticInferenceServiceRerankServiceSettings::new; + } + + @Override + protected ElasticInferenceServiceRerankServiceSettings createTestInstance() { + return createRandom(); + } + + @Override + protected ElasticInferenceServiceRerankServiceSettings mutateInstance(ElasticInferenceServiceRerankServiceSettings instance) + throws IOException { + return randomValueOtherThan(instance, ElasticInferenceServiceRerankServiceSettingsTests::createRandom); + } + + public void testFromMap() { + var modelId = "my-model-id"; + + var serviceSettings = ElasticInferenceServiceRerankServiceSettings.fromMap( + new HashMap<>(Map.of(ServiceFields.MODEL_ID, modelId)), + ConfigurationParseContext.REQUEST + ); + + assertThat(serviceSettings, is(new ElasticInferenceServiceRerankServiceSettings(modelId, null))); + } + + public void testToXContent_WritesAllFields() throws IOException { + var modelId = ".rerank-v1"; + var rateLimitSettings = new RateLimitSettings(100L); + var serviceSettings = new ElasticInferenceServiceRerankServiceSettings(modelId, rateLimitSettings); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + serviceSettings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(Strings.format(""" + {"model_id":"%s","rate_limit":{"requests_per_minute":%d}}""", modelId, rateLimitSettings.requestsPerTimeUnit()))); + } + + public static ElasticInferenceServiceRerankServiceSettings createRandom() { + return new ElasticInferenceServiceRerankServiceSettings(randomRerankModel(), null); + } + + private static String randomRerankModel() { + return randomFrom(".rerank-v1", ".rerank-v2"); + } +} From eed00f4b67f708ae173869773fb0c3e131d7e3b7 Mon Sep 17 00:00:00 2001 From: Ievgen Degtiarenko Date: Tue, 10 Jun 2025 12:54:27 +0200 Subject: [PATCH 030/102] Rename target destination for microbenchmarks (#128878) --- .buildkite/hooks/pre-command | 8 +++----- .buildkite/scripts/index-micro-benchmark-results.sh | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command index dbd5b77562106..4c8a152e17c8b 100644 --- a/.buildkite/hooks/pre-command +++ b/.buildkite/hooks/pre-command @@ -95,13 +95,11 @@ if [[ "${USE_PROD_DOCKER_CREDENTIALS:-}" == "true" ]]; then fi if [[ "${USE_PERF_CREDENTIALS:-}" == "true" ]]; then - PERF_METRICS_HOST=$(vault read -field=es_host /secret/ci/elastic-elasticsearch/esbench-metics) - PERF_METRICS_INDEX="dummy-micro-benchmarks" - PERF_METRICS_USERNAME=$(vault read -field=es_username /secret/ci/elastic-elasticsearch/esbench-metics) - PERF_METRICS_PASSWORD=$(vault read -field=es_password /secret/ci/elastic-elasticsearch/esbench-metics) + PERF_METRICS_HOST=$(vault read -field=es_host /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) + PERF_METRICS_USERNAME=$(vault read -field=es_username /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) + PERF_METRICS_PASSWORD=$(vault read -field=es_password /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) export PERF_METRICS_HOST - export PERF_METRICS_INDEX export PERF_METRICS_USERNAME export PERF_METRICS_PASSWORD fi diff --git a/.buildkite/scripts/index-micro-benchmark-results.sh b/.buildkite/scripts/index-micro-benchmark-results.sh index b7ffd82c529f1..735b68b8cc14d 100755 --- a/.buildkite/scripts/index-micro-benchmark-results.sh +++ b/.buildkite/scripts/index-micro-benchmark-results.sh @@ -3,7 +3,7 @@ jq -c '.[]' "benchmarks/build/result.json" | while read -r doc; do doc=$(echo "$doc" | jq --argjson timestamp "$(date +%s000)" '. + {"@timestamp": $timestamp}') echo "Indexing $(echo "$doc" | jq -r '.benchmark')" - curl -s -X POST "https://$PERF_METRICS_HOST/$PERF_METRICS_INDEX/_doc" \ + curl -s -X POST "https://$PERF_METRICS_HOST/metrics-microbenchmarks-default/_doc" \ -u "$PERF_METRICS_USERNAME:$PERF_METRICS_PASSWORD" \ -H 'Content-Type: application/json' \ -d "$doc" From f768664ad381601f8b21d0eda6418150cc319e7d Mon Sep 17 00:00:00 2001 From: Jan Kuipers <148754765+jan-elastic@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:36:42 +0200 Subject: [PATCH 031/102] Include direct memory and non-heap memory in ML memory calculations (take #2) (#128742) * Include direct memory and non-heap memory in ML memory calculations. * Reduce ML_ONLY heap size, so that direct memory is accounted for. * [CI] Auto commit changes from spotless * changelog * improve docs * Reuse direct memory to heap factor * feature flag --------- Co-authored-by: elasticsearchmachine --- .../server/cli/JvmErgonomics.java | 4 ++- .../server/cli/MachineDependentHeap.java | 36 +++++++++++++------ .../server/cli/MachineDependentHeapTests.java | 10 +++--- docs/changelog/128742.yaml | 5 +++ .../elasticsearch/monitor/jvm/JvmInfo.java | 17 +++++---- .../xpack/ml/MachineLearning.java | 10 +++++- 6 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 docs/changelog/128742.yaml diff --git a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmErgonomics.java b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmErgonomics.java index 1160589e43966..1970dd82b8ebe 100644 --- a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmErgonomics.java +++ b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmErgonomics.java @@ -28,6 +28,8 @@ */ final class JvmErgonomics { + static final double DIRECT_MEMORY_TO_HEAP_FACTOR = 0.5; + private JvmErgonomics() { throw new AssertionError("No instances intended"); } @@ -44,7 +46,7 @@ static List choose(final List userDefinedJvmOptions, Settings no final long heapSize = JvmOption.extractMaxHeapSize(finalJvmOptions); final long maxDirectMemorySize = JvmOption.extractMaxDirectMemorySize(finalJvmOptions); if (maxDirectMemorySize == 0) { - ergonomicChoices.add("-XX:MaxDirectMemorySize=" + heapSize / 2); + ergonomicChoices.add("-XX:MaxDirectMemorySize=" + (long) (DIRECT_MEMORY_TO_HEAP_FACTOR * heapSize)); } final boolean tuneG1GCForSmallHeap = tuneG1GCForSmallHeap(heapSize); diff --git a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/MachineDependentHeap.java b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/MachineDependentHeap.java index 26fd7294ed557..b68e374bbdb94 100644 --- a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/MachineDependentHeap.java +++ b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/MachineDependentHeap.java @@ -11,6 +11,7 @@ import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.FeatureFlag; import org.elasticsearch.node.NodeRoleSettings; import java.io.IOException; @@ -37,6 +38,8 @@ public class MachineDependentHeap { protected static final long MAX_HEAP_SIZE = GB * 31; // 31GB protected static final long MIN_HEAP_SIZE = 1024 * 1024 * 128; // 128MB + private static final FeatureFlag NEW_ML_MEMORY_COMPUTATION_FEATURE_FLAG = new FeatureFlag("new_ml_memory_computation"); + public MachineDependentHeap() {} /** @@ -76,12 +79,16 @@ protected int getHeapSizeMb(Settings nodeSettings, MachineNodeRole role, long av /* * Machine learning only node. * - *

Heap is computed as: - *

    - *
  • 40% of total system memory when total system memory 16 gigabytes or less.
  • - *
  • 40% of the first 16 gigabytes plus 10% of memory above that when total system memory is more than 16 gigabytes.
  • - *
  • The absolute maximum heap size is 31 gigabytes.
  • - *
+ * The memory reserved for Java is computed as: + * - 40% of total system memory when total system memory 16 gigabytes or less. + * - 40% of the first 16 gigabytes plus 10% of memory above that when total system memory is more than 16 gigabytes. + * - The absolute maximum heap size is 31 gigabytes. + * + * This Java memory is divided as follows: + * - 2/3 of the Java memory is reserved for the Java heap. + * - 1/3 of the Java memory is reserved for the Java direct memory. + * + * The direct memory being half of the heap is set by the JvmErgonomics class. * * In all cases the result is rounded down to the next whole multiple of 4 megabytes. * The reason for doing this is that Java will round requested heap sizes to a multiple @@ -95,13 +102,22 @@ protected int getHeapSizeMb(Settings nodeSettings, MachineNodeRole role, long av * * If this formula is changed then corresponding changes must be made to the {@code NativeMemoryCalculator} and * {@code MlAutoscalingDeciderServiceTests} classes in the ML plugin code. Failure to keep the logic synchronized - * could result in repeated autoscaling up and down. + * could result in ML processes crashing with OOM errors or repeated autoscaling up and down. */ case ML_ONLY -> { - if (availableMemory <= (GB * 16)) { - yield mb((long) (availableMemory * .4), 4); + double heapFractionBelow16GB = 0.4; + double heapFractionAbove16GB = 0.1; + if (NEW_ML_MEMORY_COMPUTATION_FEATURE_FLAG.isEnabled()) { + heapFractionBelow16GB = 0.4 / (1.0 + JvmErgonomics.DIRECT_MEMORY_TO_HEAP_FACTOR); + heapFractionAbove16GB = 0.1 / (1.0 + JvmErgonomics.DIRECT_MEMORY_TO_HEAP_FACTOR); + } + if (availableMemory <= GB * 16) { + yield mb((long) (availableMemory * heapFractionBelow16GB), 4); } else { - yield mb((long) min((GB * 16) * .4 + (availableMemory - GB * 16) * .1, MAX_HEAP_SIZE), 4); + yield mb( + (long) min(GB * 16 * heapFractionBelow16GB + (availableMemory - GB * 16) * heapFractionAbove16GB, MAX_HEAP_SIZE), + 4 + ); } } /* diff --git a/distribution/tools/server-cli/src/test/java/org/elasticsearch/server/cli/MachineDependentHeapTests.java b/distribution/tools/server-cli/src/test/java/org/elasticsearch/server/cli/MachineDependentHeapTests.java index 64b46f1bca98f..12f455f242dc0 100644 --- a/distribution/tools/server-cli/src/test/java/org/elasticsearch/server/cli/MachineDependentHeapTests.java +++ b/distribution/tools/server-cli/src/test/java/org/elasticsearch/server/cli/MachineDependentHeapTests.java @@ -56,13 +56,13 @@ public void testMasterOnlyOptions() throws Exception { } public void testMlOnlyOptions() throws Exception { - assertHeapOptions(1, containsInAnyOrder("-Xmx408m", "-Xms408m"), "ml"); - assertHeapOptions(4, containsInAnyOrder("-Xmx1636m", "-Xms1636m"), "ml"); - assertHeapOptions(32, containsInAnyOrder("-Xmx8192m", "-Xms8192m"), "ml"); - assertHeapOptions(64, containsInAnyOrder("-Xmx11468m", "-Xms11468m"), "ml"); + assertHeapOptions(1, containsInAnyOrder("-Xmx272m", "-Xms272m"), "ml"); + assertHeapOptions(4, containsInAnyOrder("-Xmx1092m", "-Xms1092m"), "ml"); + assertHeapOptions(32, containsInAnyOrder("-Xmx5460m", "-Xms5460m"), "ml"); + assertHeapOptions(64, containsInAnyOrder("-Xmx7644m", "-Xms7644m"), "ml"); // We'd never see a node this big in Cloud, but this assertion proves that the 31GB absolute maximum // eventually kicks in (because 0.4 * 16 + 0.1 * (263 - 16) > 31) - assertHeapOptions(263, containsInAnyOrder("-Xmx31744m", "-Xms31744m"), "ml"); + assertHeapOptions(263, containsInAnyOrder("-Xmx21228m", "-Xms21228m"), "ml"); } public void testDataNodeOptions() throws Exception { diff --git a/docs/changelog/128742.yaml b/docs/changelog/128742.yaml new file mode 100644 index 0000000000000..ce974301f2dfc --- /dev/null +++ b/docs/changelog/128742.yaml @@ -0,0 +1,5 @@ +pr: 128742 +summary: "Account for Java direct memory on machine learning nodes to prevent out-of-memory crashes." +area: Machine Learning +type: bug +issues: [] diff --git a/server/src/main/java/org/elasticsearch/monitor/jvm/JvmInfo.java b/server/src/main/java/org/elasticsearch/monitor/jvm/JvmInfo.java index f827bb30c7e4a..a1faf24b512c4 100644 --- a/server/src/main/java/org/elasticsearch/monitor/jvm/JvmInfo.java +++ b/server/src/main/java/org/elasticsearch/monitor/jvm/JvmInfo.java @@ -43,14 +43,7 @@ public class JvmInfo implements ReportingService.Info { long nonHeapInit = memoryMXBean.getNonHeapMemoryUsage().getInit() < 0 ? 0 : memoryMXBean.getNonHeapMemoryUsage().getInit(); long nonHeapMax = memoryMXBean.getNonHeapMemoryUsage().getMax() < 0 ? 0 : memoryMXBean.getNonHeapMemoryUsage().getMax(); long directMemoryMax = 0; - try { - Class vmClass = Class.forName("sun.misc.VM"); - directMemoryMax = (Long) vmClass.getMethod("maxDirectMemory").invoke(null); - } catch (Exception t) { - // ignore - } String[] inputArguments = runtimeMXBean.getInputArguments().toArray(new String[runtimeMXBean.getInputArguments().size()]); - Mem mem = new Mem(heapInit, heapMax, nonHeapInit, nonHeapMax, directMemoryMax); String bootClassPath; try { @@ -130,6 +123,11 @@ public class JvmInfo implements ReportingService.Info { configuredMaxHeapSize = Long.parseLong((String) valueMethod.invoke(maxHeapSizeVmOptionObject)); } catch (Exception ignored) {} + try { + Object maxDirectMemorySizeVmOptionObject = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "MaxDirectMemorySize"); + directMemoryMax = Long.parseLong((String) valueMethod.invoke(maxDirectMemorySizeVmOptionObject)); + } catch (Exception ignored) {} + try { Object useSerialGCVmOptionObject = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "UseSerialGC"); useSerialGC = (String) valueMethod.invoke(useSerialGCVmOptionObject); @@ -139,6 +137,8 @@ public class JvmInfo implements ReportingService.Info { } + Mem mem = new Mem(heapInit, heapMax, nonHeapInit, nonHeapMax, directMemoryMax); + INSTANCE = new JvmInfo( ProcessHandle.current().pid(), System.getProperty("java.version"), @@ -496,5 +496,8 @@ public ByteSizeValue getHeapMax() { return ByteSizeValue.ofBytes(heapMax); } + public ByteSizeValue getTotalMax() { + return ByteSizeValue.ofBytes(heapMax + nonHeapMax + directMemoryMax); + } } } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java index 8237a9c347ff0..6a17ad073a7f2 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java @@ -41,6 +41,7 @@ import org.elasticsearch.common.settings.SettingsModule; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.Processors; +import org.elasticsearch.common.util.FeatureFlag; import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.core.TimeValue; import org.elasticsearch.env.Environment; @@ -557,6 +558,8 @@ public class MachineLearning extends Plugin License.OperationMode.PLATINUM ); + private static final FeatureFlag NEW_ML_MEMORY_COMPUTATION_FEATURE_FLAG = new FeatureFlag("new_ml_memory_computation"); + @Override public Map getProcessors(Processor.Parameters parameters) { if (this.enabled == false) { @@ -874,7 +877,12 @@ public Settings additionalSettings() { machineMemoryAttrName, Long.toString(OsProbe.getInstance().osStats().getMem().getAdjustedTotal().getBytes()) ); - addMlNodeAttribute(additionalSettings, jvmSizeAttrName, Long.toString(Runtime.getRuntime().maxMemory())); + + long jvmSize = Runtime.getRuntime().maxMemory(); + if (NEW_ML_MEMORY_COMPUTATION_FEATURE_FLAG.isEnabled()) { + jvmSize = JvmInfo.jvmInfo().getMem().getTotalMax().getBytes(); + } + addMlNodeAttribute(additionalSettings, jvmSizeAttrName, Long.toString(jvmSize)); addMlNodeAttribute( additionalSettings, deprecatedAllocatedProcessorsAttrName, From 2d605ee7049e127ea014cfee781fd8955975bf9b Mon Sep 17 00:00:00 2001 From: Ignacio Vera Date: Tue, 10 Jun 2025 14:48:45 +0200 Subject: [PATCH 032/102] Throw better exception for unsupported aggregations over shape fields (#129139) --- .../xpack/spatial/SpatialPlugin.java | 30 +---- .../UnsupportedAggregationsTests.java | 121 ++++++++++++++++++ 2 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/UnsupportedAggregationsTests.java diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java index b9d6d5fe20e08..c8de01e326f99 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/SpatialPlugin.java @@ -26,12 +26,8 @@ import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGridAggregator; import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregator; -import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder; -import org.elasticsearch.search.aggregations.metrics.CardinalityAggregator; import org.elasticsearch.search.aggregations.metrics.GeoBoundsAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregationBuilder; -import org.elasticsearch.search.aggregations.metrics.ValueCountAggregationBuilder; -import org.elasticsearch.search.aggregations.metrics.ValueCountAggregator; import org.elasticsearch.search.aggregations.support.CoreValuesSourceType; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; @@ -162,9 +158,7 @@ public List> getAggregationExtentions() { return List.of( this::registerGeoShapeCentroidAggregator, this::registerGeoShapeGridAggregators, - SpatialPlugin::registerGeoShapeBoundsAggregator, - SpatialPlugin::registerValueCountAggregator, - SpatialPlugin::registerCardinalityAggregator + SpatialPlugin::registerGeoShapeBoundsAggregator ); } @@ -408,28 +402,6 @@ private void registerGeoShapeGridAggregators(ValuesSourceRegistry.Builder builde ); } - private static void registerValueCountAggregator(ValuesSourceRegistry.Builder builder) { - builder.register(ValueCountAggregationBuilder.REGISTRY_KEY, GeoShapeValuesSourceType.instance(), ValueCountAggregator::new, true); - } - - private static void registerCardinalityAggregator(ValuesSourceRegistry.Builder builder) { - builder.register( - CardinalityAggregationBuilder.REGISTRY_KEY, - GeoShapeValuesSourceType.instance(), - (name, valuesSourceConfig, precision, executionMode, context, parent, metadata) -> new CardinalityAggregator( - name, - valuesSourceConfig, - precision, - // Force execution mode to null - null, - context, - parent, - metadata - ), - true - ); - } - private ContextParser checkLicense(ContextParser realParser, LicensedFeature.Momentary feature) { return (parser, name) -> { if (feature.check(getLicenseState()) == false) { diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/UnsupportedAggregationsTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/UnsupportedAggregationsTests.java new file mode 100644 index 0000000000000..1e9a1b19a38d1 --- /dev/null +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/UnsupportedAggregationsTests.java @@ -0,0 +1,121 @@ +/* + * 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.spatial.search.aggregations; + +import org.apache.lucene.document.Document; +import org.elasticsearch.common.geo.Orientation; +import org.elasticsearch.geometry.Point; +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.lucene.spatial.BinaryShapeDocValuesField; +import org.elasticsearch.plugins.SearchPlugin; +import org.elasticsearch.search.aggregations.AggregatorTestCase; +import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder; +import org.elasticsearch.search.aggregations.metrics.ValueCountAggregationBuilder; +import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin; +import org.elasticsearch.xpack.spatial.index.mapper.GeoShapeWithDocValuesFieldMapper; +import org.elasticsearch.xpack.spatial.index.mapper.ShapeFieldMapper; +import org.elasticsearch.xpack.spatial.util.GeoTestUtils; + +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; + +public class UnsupportedAggregationsTests extends AggregatorTestCase { + + @Override + protected List getSearchPlugins() { + return List.of(new LocalStateSpatialPlugin()); + } + + public void testCardinalityAggregationOnGeoShape() { + MappedFieldType fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType( + "geometry", + true, + true, + randomBoolean(), + Orientation.RIGHT, + null, + null, + null, + false, + Collections.emptyMap() + ); + BinaryShapeDocValuesField field = GeoTestUtils.binaryGeoShapeDocValuesField("geometry", new Point(0, 0)); + CardinalityAggregationBuilder builder = new CardinalityAggregationBuilder("cardinality").field("geometry"); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> testCase(iw -> { + Document doc = new Document(); + doc.add(field); + iw.addDocument(doc); + }, agg -> {}, new AggTestConfig(builder, fieldType))); + assertThat(exception.getMessage(), equalTo("Field [geometry] of type [geo_shape] is not supported for aggregation [cardinality]")); + } + + public void testCardinalityAggregationOnShape() { + MappedFieldType fieldType = new ShapeFieldMapper.ShapeFieldType( + "geometry", + true, + true, + Orientation.RIGHT, + null, + false, + Collections.emptyMap() + ); + BinaryShapeDocValuesField field = GeoTestUtils.binaryCartesianShapeDocValuesField("geometry", new Point(0, 0)); + CardinalityAggregationBuilder builder = new CardinalityAggregationBuilder("cardinality").field("geometry"); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> testCase(iw -> { + Document doc = new Document(); + doc.add(field); + iw.addDocument(doc); + }, agg -> {}, new AggTestConfig(builder, fieldType))); + assertThat(exception.getMessage(), equalTo("Field [geometry] of type [shape] is not supported for aggregation [cardinality]")); + } + + public void testValueCountAggregationOnGeoShape() { + MappedFieldType fieldType = new GeoShapeWithDocValuesFieldMapper.GeoShapeWithDocValuesFieldType( + "geometry", + true, + true, + randomBoolean(), + Orientation.RIGHT, + null, + null, + null, + false, + Collections.emptyMap() + ); + BinaryShapeDocValuesField field = GeoTestUtils.binaryGeoShapeDocValuesField("geometry", new Point(0, 0)); + ValueCountAggregationBuilder builder = new ValueCountAggregationBuilder("cardinality").field("geometry"); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> testCase(iw -> { + Document doc = new Document(); + doc.add(field); + iw.addDocument(doc); + }, agg -> {}, new AggTestConfig(builder, fieldType))); + assertThat(exception.getMessage(), equalTo("Field [geometry] of type [geo_shape] is not supported for aggregation [value_count]")); + } + + public void testValueCountAggregationOShape() { + MappedFieldType fieldType = new ShapeFieldMapper.ShapeFieldType( + "geometry", + true, + true, + Orientation.RIGHT, + null, + false, + Collections.emptyMap() + ); + BinaryShapeDocValuesField field = GeoTestUtils.binaryCartesianShapeDocValuesField("geometry", new Point(0, 0)); + ValueCountAggregationBuilder builder = new ValueCountAggregationBuilder("cardinality").field("geometry"); + IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> testCase(iw -> { + Document doc = new Document(); + doc.add(field); + iw.addDocument(doc); + }, agg -> {}, new AggTestConfig(builder, fieldType))); + assertThat(exception.getMessage(), equalTo("Field [geometry] of type [shape] is not supported for aggregation [value_count]")); + } +} From b68ddd1eaa7f559db80a7bde8dfbb47ba4845b22 Mon Sep 17 00:00:00 2001 From: Mike Pellegrini Date: Tue, 10 Jun 2025 09:02:39 -0400 Subject: [PATCH 033/102] Update Test Framework To Handle Query Rewrites That Rely on Non-Null Searchers (#129160) --- .../test/AbstractQueryTestCase.java | 212 ++++++++++++------ 1 file changed, 144 insertions(+), 68 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java index 0d505dab40fed..dba46d716b643 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractQueryTestCase.java @@ -9,10 +9,14 @@ package org.elasticsearch.test; +import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.BoostQuery; +import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.store.Directory; +import org.apache.lucene.tests.index.RandomIndexWriter; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.TransportVersion; import org.elasticsearch.action.support.PlainActionFuture; @@ -45,6 +49,7 @@ import org.elasticsearch.xcontent.json.JsonStringEncoder; import org.elasticsearch.xcontent.json.JsonXContent; +import java.io.Closeable; import java.io.IOException; import java.time.Instant; import java.time.ZoneOffset; @@ -453,82 +458,87 @@ protected boolean builderGeneratesCacheableQueries() { return true; } + protected IndexReaderManager getIndexReaderManager() { + return NullIndexReaderManager.INSTANCE; + } + /** * Test creates the {@link Query} from the {@link QueryBuilder} under test and delegates the * assertions being made on the result to the implementing subclass. */ public void testToQuery() throws IOException { for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { - SearchExecutionContext context = createSearchExecutionContext(); - assert context.isCacheable(); - context.setAllowUnmappedFields(true); - QB firstQuery = createTestQueryBuilder(); - QB controlQuery = copyQuery(firstQuery); - /* we use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. - * We do it this way in SearchService where - * we first rewrite the query with a private context, then reset the context and then build the actual lucene query*/ - QueryBuilder rewritten = rewriteQuery(firstQuery, createQueryRewriteContext(), new SearchExecutionContext(context)); - Query firstLuceneQuery = rewritten.toQuery(context); - assertNotNull("toQuery should not return null", firstLuceneQuery); - assertLuceneQuery(firstQuery, firstLuceneQuery, context); - // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well - assertEquals( - "query is not equal to its copy after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, - firstQuery, - controlQuery - ); - assertEquals( - "equals is not symmetric after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, - controlQuery, - firstQuery - ); - assertThat( - "query copy's hashcode is different from original hashcode after calling toQuery, firstQuery: " - + firstQuery - + ", secondQuery: " - + controlQuery, - controlQuery.hashCode(), - equalTo(firstQuery.hashCode()) - ); - - QB secondQuery = copyQuery(firstQuery); - // query _name never should affect the result of toQuery, we randomly set it to make sure - if (randomBoolean()) { - secondQuery.queryName( - secondQuery.queryName() == null - ? randomAlphaOfLengthBetween(1, 30) - : secondQuery.queryName() + randomAlphaOfLengthBetween(1, 10) - ); - } - context = new SearchExecutionContext(context); - Query secondLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)).toQuery( - context - ); - assertNotNull("toQuery should not return null", secondLuceneQuery); - assertLuceneQuery(secondQuery, secondLuceneQuery, context); - - if (builderGeneratesCacheableQueries()) { + try (IndexReaderManager irm = getIndexReaderManager()) { + SearchExecutionContext context = createSearchExecutionContext(irm.getIndexSearcher()); + assert context.isCacheable(); + context.setAllowUnmappedFields(true); + QB firstQuery = createTestQueryBuilder(); + QB controlQuery = copyQuery(firstQuery); + /* we use a private rewrite context here since we want the most realistic way of asserting that we are cacheable or not. + * We do it this way in SearchService where + * we first rewrite the query with a private context, then reset the context and then build the actual lucene query*/ + QueryBuilder rewritten = rewriteQuery(firstQuery, createQueryRewriteContext(), new SearchExecutionContext(context)); + Query firstLuceneQuery = rewritten.toQuery(context); + assertNotNull("toQuery should not return null", firstLuceneQuery); + assertLuceneQuery(firstQuery, firstLuceneQuery, context); + // remove after assertLuceneQuery since the assertLuceneQuery impl might access the context as well assertEquals( - "two equivalent query builders lead to different lucene queries hashcode", - secondLuceneQuery.hashCode(), - firstLuceneQuery.hashCode() + "query is not equal to its copy after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, + firstQuery, + controlQuery ); assertEquals( - "two equivalent query builders lead to different lucene queries", - rewrite(secondLuceneQuery), - rewrite(firstLuceneQuery) + "equals is not symmetric after calling toQuery, firstQuery: " + firstQuery + ", secondQuery: " + controlQuery, + controlQuery, + firstQuery + ); + assertThat( + "query copy's hashcode is different from original hashcode after calling toQuery, firstQuery: " + + firstQuery + + ", secondQuery: " + + controlQuery, + controlQuery.hashCode(), + equalTo(firstQuery.hashCode()) ); - } - if (supportsBoost() && firstLuceneQuery instanceof MatchNoDocsQuery == false) { - secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); - Query thirdLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)) + QB secondQuery = copyQuery(firstQuery); + // query _name never should affect the result of toQuery, we randomly set it to make sure + if (randomBoolean()) { + secondQuery.queryName( + secondQuery.queryName() == null + ? randomAlphaOfLengthBetween(1, 30) + : secondQuery.queryName() + randomAlphaOfLengthBetween(1, 10) + ); + } + context = new SearchExecutionContext(context); + Query secondLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)) .toQuery(context); - assertNotEquals( - "modifying the boost doesn't affect the corresponding lucene query", - rewrite(firstLuceneQuery), - rewrite(thirdLuceneQuery) - ); + assertNotNull("toQuery should not return null", secondLuceneQuery); + assertLuceneQuery(secondQuery, secondLuceneQuery, context); + + if (builderGeneratesCacheableQueries()) { + assertEquals( + "two equivalent query builders lead to different lucene queries hashcode", + secondLuceneQuery.hashCode(), + firstLuceneQuery.hashCode() + ); + assertEquals( + "two equivalent query builders lead to different lucene queries", + rewrite(secondLuceneQuery), + rewrite(firstLuceneQuery) + ); + } + + if (supportsBoost() && firstLuceneQuery instanceof MatchNoDocsQuery == false) { + secondQuery.boost(firstQuery.boost() + 1f + randomFloat()); + Query thirdLuceneQuery = rewriteQuery(secondQuery, createQueryRewriteContext(), new SearchExecutionContext(context)) + .toQuery(context); + assertNotEquals( + "modifying the boost doesn't affect the corresponding lucene query", + rewrite(firstLuceneQuery), + rewrite(thirdLuceneQuery) + ); + } } } } @@ -938,9 +948,75 @@ public boolean isTextField(String fieldName) { */ public void testCacheability() throws IOException { QB queryBuilder = createTestQueryBuilder(); - SearchExecutionContext context = createSearchExecutionContext(); - QueryBuilder rewriteQuery = rewriteQuery(queryBuilder, createQueryRewriteContext(), new SearchExecutionContext(context)); - assertNotNull(rewriteQuery.toQuery(context)); - assertTrue("query should be cacheable: " + queryBuilder.toString(), context.isCacheable()); + try (IndexReaderManager irm = getIndexReaderManager()) { + SearchExecutionContext context = createSearchExecutionContext(irm.getIndexSearcher()); + QueryBuilder rewriteQuery = rewriteQuery(queryBuilder, createQueryRewriteContext(), new SearchExecutionContext(context)); + assertNotNull(rewriteQuery.toQuery(context)); + assertTrue("query should be cacheable: " + queryBuilder.toString(), context.isCacheable()); + } + } + + public static class IndexReaderManager implements Closeable { + private final Directory directory; + private RandomIndexWriter indexWriter; + private IndexReader indexReader; + private IndexSearcher indexSearcher; + + public IndexReaderManager() { + this.directory = newDirectory(); + } + + private IndexReaderManager(Directory directory) { + this.directory = directory; + } + + public IndexReader getIndexReader() throws IOException { + if (indexReader == null) { + indexWriter = new RandomIndexWriter(random(), directory); + initIndexWriter(indexWriter); + indexReader = indexWriter.getReader(); + } + return indexReader; + } + + public IndexSearcher getIndexSearcher() throws IOException { + if (indexSearcher == null) { + indexSearcher = newSearcher(getIndexReader()); + } + return indexSearcher; + } + + @Override + public void close() throws IOException { + if (indexReader != null) { + indexReader.close(); + } + if (indexWriter != null) { + indexWriter.close(); + } + if (directory != null) { + directory.close(); + } + } + + protected void initIndexWriter(RandomIndexWriter indexWriter) {} + } + + public static class NullIndexReaderManager extends IndexReaderManager { + public static final NullIndexReaderManager INSTANCE = new NullIndexReaderManager(); + + public NullIndexReaderManager() { + super(null); + } + + @Override + public IndexReader getIndexReader() { + return null; + } + + @Override + public IndexSearcher getIndexSearcher() { + return null; + } } } From f1bf18ed6b8a2a1f528a005c14db2b94ffc4aee2 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 10 Jun 2025 15:42:08 +0200 Subject: [PATCH 034/102] Update ReproduceInfoPrinter to correctly print a reproduction line for Lucene & build candidate upgrade tests (#129044) --- .../test/junit/listeners/ReproduceInfoPrinter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java b/test/framework/src/main/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java index b627a8803bf21..c187c9b822a86 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java +++ b/test/framework/src/main/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java @@ -65,7 +65,9 @@ public void testFailure(Failure failure) throws Exception { final String gradlew = Constants.WINDOWS ? "gradlew" : "./gradlew"; final StringBuilder b = new StringBuilder("REPRODUCE WITH: " + gradlew + " "); String task = System.getProperty("tests.task"); - boolean isBwcTest = Boolean.parseBoolean(System.getProperty("tests.bwc", "false")); + boolean isBwcTest = Boolean.parseBoolean(System.getProperty("tests.bwc", "false")) + || System.getProperty("tests.bwc.main.version") != null + || System.getProperty("tests.bwc.refspec.main") != null; // append Gradle test runner test filter string b.append("\"" + task + "\""); @@ -174,7 +176,9 @@ private ReproduceErrorMessageBuilder appendESProperties() { "tests.bwc", "tests.bwc.version", "build.snapshot", - "tests.configure_test_clusters_with_one_processor" + "tests.configure_test_clusters_with_one_processor", + "tests.bwc.main.version", + "tests.bwc.refspec.main" ); if (System.getProperty("tests.jvm.argline") != null && System.getProperty("tests.jvm.argline").isEmpty() == false) { appendOpt("tests.jvm.argline", "\"" + System.getProperty("tests.jvm.argline") + "\""); From 9abfe1d33f3d66f9bc96594c293b21702051e1d6 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Tue, 10 Jun 2025 15:30:33 +0100 Subject: [PATCH 035/102] Increment inference stats counter for shard bulk inference calls (#129140) This change updates the inference stats counter to include chunked inference calls performed by the shard bulk inference filter on all semantic text fields. It ensures that usage of inference on semantic text fields is properly recorded in the stats. --- docs/changelog/129140.yaml | 5 + .../xpack/inference/InferencePlugin.java | 12 ++- .../ShardBulkInferenceActionFilter.java | 23 ++++- .../ShardBulkInferenceActionFilterTests.java | 96 ++++++++++++++++--- 4 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 docs/changelog/129140.yaml diff --git a/docs/changelog/129140.yaml b/docs/changelog/129140.yaml new file mode 100644 index 0000000000000..e7ee59122c34f --- /dev/null +++ b/docs/changelog/129140.yaml @@ -0,0 +1,5 @@ +pr: 129140 +summary: Increment inference stats counter for shard bulk inference calls +area: Machine Learning +type: enhancement +issues: [] diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java index 915a4d3f7af9b..2709d9de19c5c 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferencePlugin.java @@ -344,22 +344,24 @@ public Collection createComponents(PluginServices services) { } inferenceServiceRegistry.set(serviceRegistry); + var meterRegistry = services.telemetryProvider().getMeterRegistry(); + var inferenceStats = InferenceStats.create(meterRegistry); + var inferenceStatsBinding = new PluginComponentBinding<>(InferenceStats.class, inferenceStats); + var actionFilter = new ShardBulkInferenceActionFilter( services.clusterService(), serviceRegistry, modelRegistry.get(), getLicenseState(), - services.indexingPressure() + services.indexingPressure(), + inferenceStats ); shardBulkInferenceActionFilter.set(actionFilter); - var meterRegistry = services.telemetryProvider().getMeterRegistry(); - var inferenceStats = new PluginComponentBinding<>(InferenceStats.class, InferenceStats.create(meterRegistry)); - components.add(serviceRegistry); components.add(modelRegistry.get()); components.add(httpClientManager); - components.add(inferenceStats); + components.add(inferenceStatsBinding); // Only add InferenceServiceNodeLocalRateLimitCalculator (which is a ClusterStateListener) for cluster aware rate limiting, // if the rate limiting feature flags are enabled, otherwise provide noop implementation diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java index a4ab8663e8664..082ece347208a 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java @@ -63,6 +63,7 @@ import org.elasticsearch.xpack.inference.mapper.SemanticTextFieldMapper; import org.elasticsearch.xpack.inference.mapper.SemanticTextUtils; import org.elasticsearch.xpack.inference.registry.ModelRegistry; +import org.elasticsearch.xpack.inference.telemetry.InferenceStats; import java.io.IOException; import java.util.ArrayList; @@ -78,6 +79,8 @@ import static org.elasticsearch.xpack.inference.InferencePlugin.INFERENCE_API_FEATURE; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.toSemanticTextFieldChunks; import static org.elasticsearch.xpack.inference.mapper.SemanticTextField.toSemanticTextFieldChunksLegacy; +import static org.elasticsearch.xpack.inference.telemetry.InferenceStats.modelAttributes; +import static org.elasticsearch.xpack.inference.telemetry.InferenceStats.responseAttributes; /** * A {@link MappedActionFilter} that intercepts {@link BulkShardRequest} to apply inference on fields specified @@ -112,6 +115,7 @@ public class ShardBulkInferenceActionFilter implements MappedActionFilter { private final ModelRegistry modelRegistry; private final XPackLicenseState licenseState; private final IndexingPressure indexingPressure; + private final InferenceStats inferenceStats; private volatile long batchSizeInBytes; public ShardBulkInferenceActionFilter( @@ -119,13 +123,15 @@ public ShardBulkInferenceActionFilter( InferenceServiceRegistry inferenceServiceRegistry, ModelRegistry modelRegistry, XPackLicenseState licenseState, - IndexingPressure indexingPressure + IndexingPressure indexingPressure, + InferenceStats inferenceStats ) { this.clusterService = clusterService; this.inferenceServiceRegistry = inferenceServiceRegistry; this.modelRegistry = modelRegistry; this.licenseState = licenseState; this.indexingPressure = indexingPressure; + this.inferenceStats = inferenceStats; this.batchSizeInBytes = INDICES_INFERENCE_BATCH_SIZE.get(clusterService.getSettings()).getBytes(); clusterService.getClusterSettings().addSettingsUpdateConsumer(INDICES_INFERENCE_BATCH_SIZE, this::setBatchSize); } @@ -386,10 +392,12 @@ public void onFailure(Exception exc) { public void onResponse(List results) { try (onFinish) { var requestsIterator = requests.iterator(); + int success = 0; for (ChunkedInference result : results) { var request = requestsIterator.next(); var acc = inferenceResults.get(request.bulkItemIndex); if (result instanceof ChunkedInferenceError error) { + recordRequestCountMetrics(inferenceProvider.model, 1, error.exception()); acc.addFailure( new InferenceException( "Exception when running inference id [{}] on field [{}]", @@ -399,6 +407,7 @@ public void onResponse(List results) { ) ); } else { + success++; acc.addOrUpdateResponse( new FieldInferenceResponse( request.field(), @@ -412,12 +421,16 @@ public void onResponse(List results) { ); } } + if (success > 0) { + recordRequestCountMetrics(inferenceProvider.model, success, null); + } } } @Override public void onFailure(Exception exc) { try (onFinish) { + recordRequestCountMetrics(inferenceProvider.model, requests.size(), exc); for (FieldInferenceRequest request : requests) { addInferenceResponseFailure( request.bulkItemIndex, @@ -444,6 +457,14 @@ public void onFailure(Exception exc) { ); } + private void recordRequestCountMetrics(Model model, int incrementBy, Throwable throwable) { + Map requestCountAttributes = new HashMap<>(); + requestCountAttributes.putAll(modelAttributes(model)); + requestCountAttributes.putAll(responseAttributes(throwable)); + requestCountAttributes.put("inference_source", "semantic_text_bulk"); + inferenceStats.requestCount().incrementBy(incrementBy, requestCountAttributes); + } + /** * Adds all inference requests associated with their respective inference IDs to the given {@code requestsMap} * for the specified {@code item}. diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilterTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilterTests.java index f592774b7a356..a7cb0234aee59 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilterTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilterTests.java @@ -66,6 +66,7 @@ import org.elasticsearch.xpack.inference.mapper.SemanticTextField; import org.elasticsearch.xpack.inference.model.TestModel; import org.elasticsearch.xpack.inference.registry.ModelRegistry; +import org.elasticsearch.xpack.inference.telemetry.InferenceStats; import org.junit.After; import org.junit.Before; import org.mockito.stubbing.Answer; @@ -80,6 +81,7 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static org.elasticsearch.index.IndexingPressure.MAX_COORDINATING_BYTES; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; @@ -103,9 +105,11 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -127,7 +131,9 @@ public ShardBulkInferenceActionFilterTests(boolean useLegacyFormat) { @ParametersFactory public static Iterable parameters() throws Exception { - return List.of(new Object[] { true }, new Object[] { false }); + List lst = new ArrayList<>(); + lst.add(new Object[] { true }); + return lst; } @Before @@ -142,7 +148,15 @@ public void tearDownThreadPool() throws Exception { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testFilterNoop() throws Exception { - ShardBulkInferenceActionFilter filter = createFilter(threadPool, Map.of(), NOOP_INDEXING_PRESSURE, useLegacyFormat, true); + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); + ShardBulkInferenceActionFilter filter = createFilter( + threadPool, + Map.of(), + NOOP_INDEXING_PRESSURE, + useLegacyFormat, + true, + inferenceStats + ); CountDownLatch chainExecuted = new CountDownLatch(1); ActionFilterChain actionFilterChain = (task, action, request, listener) -> { try { @@ -167,8 +181,16 @@ public void testFilterNoop() throws Exception { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testLicenseInvalidForInference() throws InterruptedException { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); StaticModel model = StaticModel.createRandomInstance(); - ShardBulkInferenceActionFilter filter = createFilter(threadPool, Map.of(), NOOP_INDEXING_PRESSURE, useLegacyFormat, false); + ShardBulkInferenceActionFilter filter = createFilter( + threadPool, + Map.of(), + NOOP_INDEXING_PRESSURE, + useLegacyFormat, + false, + inferenceStats + ); CountDownLatch chainExecuted = new CountDownLatch(1); ActionFilterChain actionFilterChain = (task, action, request, listener) -> { try { @@ -205,13 +227,15 @@ public void testLicenseInvalidForInference() throws InterruptedException { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testInferenceNotFound() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); StaticModel model = StaticModel.createRandomInstance(); ShardBulkInferenceActionFilter filter = createFilter( threadPool, Map.of(model.getInferenceEntityId(), model), NOOP_INDEXING_PRESSURE, useLegacyFormat, - true + true, + inferenceStats ); CountDownLatch chainExecuted = new CountDownLatch(1); ActionFilterChain actionFilterChain = (task, action, request, listener) -> { @@ -251,14 +275,15 @@ public void testInferenceNotFound() throws Exception { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testItemFailures() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); StaticModel model = StaticModel.createRandomInstance(TaskType.SPARSE_EMBEDDING); - ShardBulkInferenceActionFilter filter = createFilter( threadPool, Map.of(model.getInferenceEntityId(), model), NOOP_INDEXING_PRESSURE, useLegacyFormat, - true + true, + inferenceStats ); model.putResult("I am a failure", new ChunkedInferenceError(new IllegalArgumentException("boom"))); model.putResult("I am a success", randomChunkedInferenceEmbedding(model, List.of("I am a success"))); @@ -316,10 +341,30 @@ public void testItemFailures() throws Exception { request.setInferenceFieldMap(inferenceFieldMap); filter.apply(task, TransportShardBulkAction.ACTION_NAME, request, actionListener, actionFilterChain); awaitLatch(chainExecuted, 10, TimeUnit.SECONDS); + + AtomicInteger success = new AtomicInteger(0); + AtomicInteger failed = new AtomicInteger(0); + verify(inferenceStats.requestCount(), atMost(3)).incrementBy(anyLong(), assertArg(attributes -> { + var statusCode = attributes.get("status_code"); + if (statusCode == null) { + failed.incrementAndGet(); + assertThat(attributes.get("error.type"), is("IllegalArgumentException")); + } else { + success.incrementAndGet(); + assertThat(statusCode, is(200)); + } + assertThat(attributes.get("task_type"), is(model.getTaskType().toString())); + assertThat(attributes.get("model_id"), is(model.getServiceSettings().modelId())); + assertThat(attributes.get("service"), is(model.getConfigurations().getService())); + assertThat(attributes.get("inference_source"), is("semantic_text_bulk")); + })); + assertThat(success.get(), equalTo(1)); + assertThat(failed.get(), equalTo(2)); } @SuppressWarnings({ "unchecked", "rawtypes" }) public void testExplicitNull() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); StaticModel model = StaticModel.createRandomInstance(TaskType.SPARSE_EMBEDDING); model.putResult("I am a failure", new ChunkedInferenceError(new IllegalArgumentException("boom"))); model.putResult("I am a success", randomChunkedInferenceEmbedding(model, List.of("I am a success"))); @@ -329,7 +374,8 @@ public void testExplicitNull() throws Exception { Map.of(model.getInferenceEntityId(), model), NOOP_INDEXING_PRESSURE, useLegacyFormat, - true + true, + inferenceStats ); CountDownLatch chainExecuted = new CountDownLatch(1); @@ -394,13 +440,15 @@ public void testExplicitNull() throws Exception { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testHandleEmptyInput() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); StaticModel model = StaticModel.createRandomInstance(); ShardBulkInferenceActionFilter filter = createFilter( threadPool, Map.of(model.getInferenceEntityId(), model), NOOP_INDEXING_PRESSURE, useLegacyFormat, - true + true, + inferenceStats ); CountDownLatch chainExecuted = new CountDownLatch(1); @@ -447,6 +495,7 @@ public void testHandleEmptyInput() throws Exception { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testManyRandomDocs() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); Map inferenceModelMap = new HashMap<>(); int numModels = randomIntBetween(1, 3); for (int i = 0; i < numModels; i++) { @@ -471,7 +520,14 @@ public void testManyRandomDocs() throws Exception { modifiedRequests[id] = res[1]; } - ShardBulkInferenceActionFilter filter = createFilter(threadPool, inferenceModelMap, NOOP_INDEXING_PRESSURE, useLegacyFormat, true); + ShardBulkInferenceActionFilter filter = createFilter( + threadPool, + inferenceModelMap, + NOOP_INDEXING_PRESSURE, + useLegacyFormat, + true, + inferenceStats + ); CountDownLatch chainExecuted = new CountDownLatch(1); ActionFilterChain actionFilterChain = (task, action, request, listener) -> { try { @@ -503,6 +559,7 @@ public void testManyRandomDocs() throws Exception { @SuppressWarnings({ "unchecked", "rawtypes" }) public void testIndexingPressure() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); final InstrumentedIndexingPressure indexingPressure = new InstrumentedIndexingPressure(Settings.EMPTY); final StaticModel sparseModel = StaticModel.createRandomInstance(TaskType.SPARSE_EMBEDDING); final StaticModel denseModel = StaticModel.createRandomInstance(TaskType.TEXT_EMBEDDING); @@ -511,7 +568,8 @@ public void testIndexingPressure() throws Exception { Map.of(sparseModel.getInferenceEntityId(), sparseModel, denseModel.getInferenceEntityId(), denseModel), indexingPressure, useLegacyFormat, - true + true, + inferenceStats ); XContentBuilder doc0Source = IndexRequest.getXContentBuilder(XContentType.JSON, "sparse_field", "a test value"); @@ -619,6 +677,7 @@ public void testIndexingPressure() throws Exception { @SuppressWarnings("unchecked") public void testIndexingPressureTripsOnInferenceRequestGeneration() throws Exception { + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); final InstrumentedIndexingPressure indexingPressure = new InstrumentedIndexingPressure( Settings.builder().put(MAX_COORDINATING_BYTES.getKey(), "1b").build() ); @@ -628,7 +687,8 @@ public void testIndexingPressureTripsOnInferenceRequestGeneration() throws Excep Map.of(sparseModel.getInferenceEntityId(), sparseModel), indexingPressure, useLegacyFormat, - true + true, + inferenceStats ); XContentBuilder doc1Source = IndexRequest.getXContentBuilder(XContentType.JSON, "sparse_field", "bar"); @@ -702,6 +762,7 @@ public void testIndexingPressureTripsOnInferenceResponseHandling() throws Except Settings.builder().put(MAX_COORDINATING_BYTES.getKey(), (bytesUsed(doc1Source) + 1) + "b").build() ); + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); final StaticModel sparseModel = StaticModel.createRandomInstance(TaskType.SPARSE_EMBEDDING); sparseModel.putResult("bar", randomChunkedInferenceEmbedding(sparseModel, List.of("bar"))); @@ -710,7 +771,8 @@ public void testIndexingPressureTripsOnInferenceResponseHandling() throws Except Map.of(sparseModel.getInferenceEntityId(), sparseModel), indexingPressure, useLegacyFormat, - true + true, + inferenceStats ); CountDownLatch chainExecuted = new CountDownLatch(1); @@ -813,12 +875,14 @@ public void testIndexingPressurePartialFailure() throws Exception { .build() ); + final InferenceStats inferenceStats = new InferenceStats(mock(), mock()); final ShardBulkInferenceActionFilter filter = createFilter( threadPool, Map.of(sparseModel.getInferenceEntityId(), sparseModel), indexingPressure, useLegacyFormat, - true + true, + inferenceStats ); CountDownLatch chainExecuted = new CountDownLatch(1); @@ -893,7 +957,8 @@ private static ShardBulkInferenceActionFilter createFilter( Map modelMap, IndexingPressure indexingPressure, boolean useLegacyFormat, - boolean isLicenseValidForInference + boolean isLicenseValidForInference, + InferenceStats inferenceStats ) { ModelRegistry modelRegistry = mock(ModelRegistry.class); Answer unparsedModelAnswer = invocationOnMock -> { @@ -970,7 +1035,8 @@ private static ShardBulkInferenceActionFilter createFilter( inferenceServiceRegistry, modelRegistry, licenseState, - indexingPressure + indexingPressure, + inferenceStats ); } From 2fa185a551079b3147e48f0a52cac5816e0e3427 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Tue, 10 Jun 2025 16:32:47 +0200 Subject: [PATCH 036/102] Synthetic source: avoid storing multi fields of type text and match_only_text by default. (#129126) Don't store text and match_only_text field by default when source mode is synthetic and a field is a multi field or when there is a suitable multi field. Without this change, ES would store field otherwise twice in a multi-field configuration. For example: ``` ... "os": { "properties": { "name": { "ignore_above": 1024, "type": "keyword", "fields": { "text": { "type": "match_only_text" } } } ... ``` In this case, two stored fields were added, one in case for the `name` field and one for `name.text` multi-field. This change prevents this, and would never store a stored field when text or match_only_text field is a multi-field. --- docs/changelog/129126.yaml | 6 ++ .../extras/MatchOnlyTextFieldMapper.java | 34 +++---- .../extras/MatchOnlyTextFieldMapperTests.java | 90 +++++++++++++++++++ .../elasticsearch/index/IndexVersions.java | 1 + .../index/mapper/TextFieldMapper.java | 39 ++++++-- .../index/mapper/TextFieldMapperTests.java | 67 ++++++++++++++ 6 files changed, 214 insertions(+), 23 deletions(-) create mode 100644 docs/changelog/129126.yaml diff --git a/docs/changelog/129126.yaml b/docs/changelog/129126.yaml new file mode 100644 index 0000000000000..b719af9892ba3 --- /dev/null +++ b/docs/changelog/129126.yaml @@ -0,0 +1,6 @@ +pr: 129126 +summary: "Synthetic source: avoid storing multi fields of type text and `match_only_text`\ + \ by default" +area: Mapping +type: bug +issues: [] diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapper.java index 055f6091ac484..1f799cc6d3d4c 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapper.java @@ -33,6 +33,7 @@ import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.index.IndexVersions; import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.analysis.NamedAnalyzer; import org.elasticsearch.index.fielddata.FieldDataContext; @@ -101,12 +102,9 @@ public static class Builder extends FieldMapper.Builder { private final Parameter> meta = Parameter.metaParam(); private final TextParams.Analyzers analyzers; + private final boolean withinMultiField; - public Builder(String name, IndexAnalyzers indexAnalyzers) { - this(name, IndexVersion.current(), indexAnalyzers); - } - - public Builder(String name, IndexVersion indexCreatedVersion, IndexAnalyzers indexAnalyzers) { + public Builder(String name, IndexVersion indexCreatedVersion, IndexAnalyzers indexAnalyzers, boolean withinMultiField) { super(name); this.indexCreatedVersion = indexCreatedVersion; this.analyzers = new TextParams.Analyzers( @@ -115,6 +113,7 @@ public Builder(String name, IndexVersion indexCreatedVersion, IndexAnalyzers ind m -> ((MatchOnlyTextFieldMapper) m).positionIncrementGap, indexCreatedVersion ); + this.withinMultiField = withinMultiField; } @Override @@ -140,18 +139,21 @@ private MatchOnlyTextFieldType buildFieldType(MapperBuilderContext context) { @Override public MatchOnlyTextFieldMapper build(MapperBuilderContext context) { MatchOnlyTextFieldType tft = buildFieldType(context); - return new MatchOnlyTextFieldMapper( - leafName(), - Defaults.FIELD_TYPE, - tft, - builderParams(this, context), - context.isSourceSynthetic(), - this - ); + final boolean storeSource; + if (indexCreatedVersion.onOrAfter(IndexVersions.MAPPER_TEXT_MATCH_ONLY_MULTI_FIELDS_DEFAULT_NOT_STORED)) { + storeSource = context.isSourceSynthetic() + && withinMultiField == false + && multiFieldsBuilder.hasSyntheticSourceCompatibleKeywordField() == false; + } else { + storeSource = context.isSourceSynthetic(); + } + return new MatchOnlyTextFieldMapper(leafName(), Defaults.FIELD_TYPE, tft, builderParams(this, context), storeSource, this); } } - public static final TypeParser PARSER = new TypeParser((n, c) -> new Builder(n, c.indexVersionCreated(), c.getIndexAnalyzers())); + public static final TypeParser PARSER = new TypeParser( + (n, c) -> new Builder(n, c.indexVersionCreated(), c.getIndexAnalyzers(), c.isWithinMultiField()) + ); public static class MatchOnlyTextFieldType extends StringFieldType { @@ -406,6 +408,7 @@ private String storedFieldNameForSyntheticSource() { private final int positionIncrementGap; private final boolean storeSource; private final FieldType fieldType; + private final boolean withinMultiField; private MatchOnlyTextFieldMapper( String simpleName, @@ -424,6 +427,7 @@ private MatchOnlyTextFieldMapper( this.indexAnalyzer = builder.analyzers.getIndexAnalyzer(); this.positionIncrementGap = builder.analyzers.positionIncrementGap.getValue(); this.storeSource = storeSource; + this.withinMultiField = builder.withinMultiField; } @Override @@ -433,7 +437,7 @@ public Map indexAnalyzers() { @Override public FieldMapper.Builder getMergeBuilder() { - return new Builder(leafName(), indexCreatedVersion, indexAnalyzers).init(this); + return new Builder(leafName(), indexCreatedVersion, indexAnalyzers, withinMultiField).init(this); } @Override diff --git a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapperTests.java b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapperTests.java index b913edb1f2791..22841f8c42bf1 100644 --- a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapperTests.java +++ b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/MatchOnlyTextFieldMapperTests.java @@ -23,6 +23,7 @@ import org.apache.lucene.tests.index.RandomIndexWriter; import org.elasticsearch.common.Strings; import org.elasticsearch.core.Tuple; +import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.KeywordFieldMapper; import org.elasticsearch.index.mapper.LuceneDocument; @@ -46,8 +47,10 @@ import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.core.Is.is; public class MatchOnlyTextFieldMapperTests extends MapperTestCase { @@ -255,4 +258,91 @@ public void testDocValuesLoadedFromSynthetic() throws IOException { protected IngestScriptSupport ingestScriptSupport() { throw new AssumptionViolatedException("not supported"); } + + public void testStoreParameterDefaultsSyntheticSource() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "match_only_text"); + b.endObject(); + }); + DocumentMapper mapper = createMapperService(indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + + { + List fields = doc.rootDoc().getFields("name"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(false)); + } + { + List fields = doc.rootDoc().getFields("name._original"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(true)); + } + } + + public void testStoreParameterDefaultsSyntheticSourceWithKeywordMultiField() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "match_only_text"); + b.startObject("fields"); + b.startObject("keyword"); + b.field("type", "keyword"); + b.endObject(); + b.endObject(); + b.endObject(); + }); + DocumentMapper mapper = createMapperService(indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + { + List fields = doc.rootDoc().getFields("name"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(false)); + } + { + List fields = doc.rootDoc().getFields("name._original"); + assertThat(fields, empty()); + } + } + + public void testStoreParameterDefaultsSyntheticSourceTextFieldIsMultiField() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("text"); + b.field("type", "match_only_text"); + b.endObject(); + b.endObject(); + b.endObject(); + }); + DocumentMapper mapper = createMapperService(indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + { + List fields = doc.rootDoc().getFields("name.text"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(false)); + } + { + List fields = doc.rootDoc().getFields("name.text._original"); + assertThat(fields, empty()); + } + } } diff --git a/server/src/main/java/org/elasticsearch/index/IndexVersions.java b/server/src/main/java/org/elasticsearch/index/IndexVersions.java index f32d4d7a2a302..970b63081225d 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexVersions.java +++ b/server/src/main/java/org/elasticsearch/index/IndexVersions.java @@ -171,6 +171,7 @@ private static Version parseUnchecked(String version) { public static final IndexVersion DEFAULT_TO_ACORN_HNSW_FILTER_HEURISTIC = def(9_026_0_00, Version.LUCENE_10_2_1); public static final IndexVersion SEQ_NO_WITHOUT_POINTS = def(9_027_0_00, Version.LUCENE_10_2_1); public static final IndexVersion INDEX_INT_SORT_INT_TYPE = def(9_028_0_00, Version.LUCENE_10_2_1); + public static final IndexVersion MAPPER_TEXT_MATCH_ONLY_MULTI_FIELDS_DEFAULT_NOT_STORED = def(9_029_0_00, Version.LUCENE_10_2_1); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java index 08cef586e1438..0e49506cd92e7 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java @@ -287,11 +287,19 @@ public static class Builder extends FieldMapper.Builder { final TextParams.Analyzers analyzers; + private final boolean withinMultiField; + public Builder(String name, IndexAnalyzers indexAnalyzers, boolean isSyntheticSourceEnabled) { - this(name, IndexVersion.current(), indexAnalyzers, isSyntheticSourceEnabled); + this(name, IndexVersion.current(), indexAnalyzers, isSyntheticSourceEnabled, false); } - public Builder(String name, IndexVersion indexCreatedVersion, IndexAnalyzers indexAnalyzers, boolean isSyntheticSourceEnabled) { + public Builder( + String name, + IndexVersion indexCreatedVersion, + IndexAnalyzers indexAnalyzers, + boolean isSyntheticSourceEnabled, + boolean withinMultiField + ) { super(name); // If synthetic source is used we need to either store this field @@ -300,10 +308,17 @@ public Builder(String name, IndexVersion indexCreatedVersion, IndexAnalyzers ind // storing the field without requiring users to explicitly set 'store'. // // If 'store' parameter was explicitly provided we'll reject the request. - this.store = Parameter.storeParam( - m -> ((TextFieldMapper) m).store, - () -> isSyntheticSourceEnabled && multiFieldsBuilder.hasSyntheticSourceCompatibleKeywordField() == false - ); + // Note that if current builder is a multi field, then we don't need to store, given that responsibility lies with parent field + this.withinMultiField = withinMultiField; + this.store = Parameter.storeParam(m -> ((TextFieldMapper) m).store, () -> { + if (indexCreatedVersion.onOrAfter(IndexVersions.MAPPER_TEXT_MATCH_ONLY_MULTI_FIELDS_DEFAULT_NOT_STORED)) { + return isSyntheticSourceEnabled + && this.withinMultiField == false + && multiFieldsBuilder.hasSyntheticSourceCompatibleKeywordField() == false; + } else { + return isSyntheticSourceEnabled; + } + }); this.indexCreatedVersion = indexCreatedVersion; this.analyzers = new TextParams.Analyzers( indexAnalyzers, @@ -482,7 +497,13 @@ public TextFieldMapper build(MapperBuilderContext context) { } public static final TypeParser PARSER = createTypeParserWithLegacySupport( - (n, c) -> new Builder(n, c.indexVersionCreated(), c.getIndexAnalyzers(), SourceFieldMapper.isSynthetic(c.getIndexSettings())) + (n, c) -> new Builder( + n, + c.indexVersionCreated(), + c.getIndexAnalyzers(), + SourceFieldMapper.isSynthetic(c.getIndexSettings()), + c.isWithinMultiField() + ) ); private static class PhraseWrappedAnalyzer extends AnalyzerWrapper { @@ -1304,6 +1325,7 @@ public Query existsQuery(SearchExecutionContext context) { private final SubFieldInfo phraseFieldInfo; private final boolean isSyntheticSourceEnabled; + private final boolean isWithinMultiField; private TextFieldMapper( String simpleName, @@ -1337,6 +1359,7 @@ private TextFieldMapper( this.freqFilter = builder.freqFilter.getValue(); this.fieldData = builder.fieldData.get(); this.isSyntheticSourceEnabled = builder.isSyntheticSourceEnabled; + this.isWithinMultiField = builder.withinMultiField; } @Override @@ -1360,7 +1383,7 @@ public Map indexAnalyzers() { @Override public FieldMapper.Builder getMergeBuilder() { - return new Builder(leafName(), indexCreatedVersion, indexAnalyzers, isSyntheticSourceEnabled).init(this); + return new Builder(leafName(), indexCreatedVersion, indexAnalyzers, isSyntheticSourceEnabled, isWithinMultiField).init(this); } @Override diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java index 4670738c1d210..fa2fce306ff8a 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java @@ -307,6 +307,73 @@ public void testStoreParameterDefaults() throws IOException { } } + public void testStoreParameterDefaultsSyntheticSource() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "text"); + b.endObject(); + }); + DocumentMapper mapper = createMapperService(indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + List fields = doc.rootDoc().getFields("name"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(true)); + } + + public void testStoreParameterDefaultsSyntheticSourceWithKeywordMultiField() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "text"); + b.startObject("fields"); + b.startObject("keyword"); + b.field("type", "keyword"); + b.endObject(); + b.endObject(); + b.endObject(); + }); + DocumentMapper mapper = createMapperService(indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + List fields = doc.rootDoc().getFields("name"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(false)); + } + + public void testStoreParameterDefaultsSyntheticSourceTextFieldIsMultiField() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("text"); + b.field("type", "text"); + b.endObject(); + b.endObject(); + b.endObject(); + }); + DocumentMapper mapper = createMapperService(indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + List fields = doc.rootDoc().getFields("name.text"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(false)); + } + public void testBWCSerialization() throws IOException { MapperService mapperService = createMapperService(fieldMapping(b -> { b.field("type", "text"); From ac213d5d9cecd70a01ad948771db8dfec3e1b7d9 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Tue, 10 Jun 2025 11:13:24 -0400 Subject: [PATCH 037/102] Adding `scheduled_report_id` field to kibana reporting template (#127827) * Adding scheduled_report_id field to kibana reporting template * Incrementing stack template registry version --- .../src/main/resources/kibana-reporting@template.json | 3 +++ .../org/elasticsearch/xpack/stack/StackTemplateRegistry.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/core/template-resources/src/main/resources/kibana-reporting@template.json b/x-pack/plugin/core/template-resources/src/main/resources/kibana-reporting@template.json index 6680d0ccef92a..109979208c496 100644 --- a/x-pack/plugin/core/template-resources/src/main/resources/kibana-reporting@template.json +++ b/x-pack/plugin/core/template-resources/src/main/resources/kibana-reporting@template.json @@ -42,6 +42,9 @@ "jobtype": { "type": "keyword" }, + "scheduled_report_id": { + "type": "keyword" + }, "payload": { "type": "object", "enabled": false diff --git a/x-pack/plugin/stack/src/main/java/org/elasticsearch/xpack/stack/StackTemplateRegistry.java b/x-pack/plugin/stack/src/main/java/org/elasticsearch/xpack/stack/StackTemplateRegistry.java index 82275bf2c24c8..4a2d3e2e228de 100644 --- a/x-pack/plugin/stack/src/main/java/org/elasticsearch/xpack/stack/StackTemplateRegistry.java +++ b/x-pack/plugin/stack/src/main/java/org/elasticsearch/xpack/stack/StackTemplateRegistry.java @@ -38,7 +38,7 @@ public class StackTemplateRegistry extends IndexTemplateRegistry { // The stack template registry version. This number must be incremented when we make changes // to built-in templates. - public static final int REGISTRY_VERSION = 15; + public static final int REGISTRY_VERSION = 16; public static final String TEMPLATE_VERSION_VARIABLE = "xpack.stack.template.version"; public static final Setting STACK_TEMPLATES_ENABLED = Setting.boolSetting( From 01de61ebffdb70cb3f218a613c53cc7d7aba3b82 Mon Sep 17 00:00:00 2001 From: Ioana Tagirta Date: Tue, 10 Jun 2025 17:15:40 +0200 Subject: [PATCH 038/102] ES|QL: Add FORK generative tests (#129135) --- .../esql/qa/single_node/GenerativeForkIT.java | 50 ++++++++++++++ .../xpack/esql/qa/rest/EsqlSpecTestCase.java | 10 ++- .../generative/GenerativeForkRestTest.java | 65 ++++++++++++++++++ .../xpack/esql/action/ForkIT.java | 67 ++++++++++++++++++- .../xpack/esql/analysis/Analyzer.java | 18 ++--- 5 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java create mode 100644 x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java new file mode 100644 index 0000000000000..d95cd0aecda0c --- /dev/null +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/GenerativeForkIT.java @@ -0,0 +1,50 @@ +/* + * 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.qa.single_node; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; + +import org.elasticsearch.test.TestClustersThreadFilter; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.xpack.esql.CsvSpecReader; +import org.elasticsearch.xpack.esql.qa.rest.generative.GenerativeForkRestTest; +import org.junit.ClassRule; + +@ThreadLeakFilters(filters = TestClustersThreadFilter.class) +public class GenerativeForkIT extends GenerativeForkRestTest { + @ClassRule + public static ElasticsearchCluster cluster = Clusters.testCluster(spec -> spec.plugin("inference-service-test")); + + @Override + protected String getTestRestCluster() { + return cluster.getHttpAddresses(); + } + + public GenerativeForkIT( + String fileName, + String groupName, + String testName, + Integer lineNumber, + CsvSpecReader.CsvTestCase testCase, + String instructions, + Mode mode + ) { + super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + } + + @Override + protected boolean enableRoundingDoubleValuesOnAsserting() { + // This suite runs with more than one node and three shards in serverless + return cluster.getNumNodes() > 1; + } + + @Override + protected boolean supportsSourceFieldMapping() { + return cluster.getNumNodes() == 1; + } +} diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java index 69df40899a0a8..e4c8b67d4eb72 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlSpecTestCase.java @@ -259,15 +259,19 @@ protected boolean supportsSourceFieldMapping() throws IOException { return true; } - protected final void doTest() throws Throwable { + protected void doTest() throws Throwable { + doTest(testCase.query); + } + + protected final void doTest(String query) throws Throwable { RequestObjectBuilder builder = new RequestObjectBuilder(randomFrom(XContentType.values())); - if (testCase.query.toUpperCase(Locale.ROOT).contains("LOOKUP_\uD83D\uDC14")) { + if (query.toUpperCase(Locale.ROOT).contains("LOOKUP_\uD83D\uDC14")) { builder.tables(tables()); } Map prevTooks = supportsTook() ? tooks() : null; - Map answer = runEsql(builder.query(testCase.query), testCase.assertWarnings(deduplicateExactWarnings())); + Map answer = runEsql(builder.query(query), testCase.assertWarnings(deduplicateExactWarnings())); var expectedColumnsWithValues = loadCsvSpecValues(testCase.expectedResults); diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java new file mode 100644 index 0000000000000..9cfbc7e69b6c5 --- /dev/null +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java @@ -0,0 +1,65 @@ +/* + * 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.qa.rest.generative; + +import org.elasticsearch.xpack.esql.CsvSpecReader; +import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.*; + +/** + * Tests for FORK. We generate tests for FORK from existing CSV tests. + * We append a `| FORK (WHERE true) (WHERE true) | WHERE _fork == "fork1" | DROP _fork` suffix to existing + * CSV test cases. This will produce a query that executes multiple FORK branches but expects the same results + * as the initial CSV test case. + * For now, we skip tests that already require FORK, since multiple FORK commands are not allowed. + */ +public abstract class GenerativeForkRestTest extends EsqlSpecTestCase { + public GenerativeForkRestTest( + String fileName, + String groupName, + String testName, + Integer lineNumber, + CsvSpecReader.CsvTestCase testCase, + String instructions, + Mode mode + ) { + super(fileName, groupName, testName, lineNumber, testCase, instructions, mode); + } + + @Override + protected void doTest() throws Throwable { + String query = testCase.query + " | FORK (WHERE true) (WHERE true) | WHERE _fork == \"fork1\" | DROP _fork"; + doTest(query); + } + + @Override + protected void shouldSkipTest(String testName) throws IOException { + super.shouldSkipTest(testName); + + assumeFalse( + "Tests using FORK or RRF already are skipped since we don't support multiple FORKs", + testCase.requiredCapabilities.contains(FORK_V7.capabilityName()) || testCase.requiredCapabilities.contains(RRF.capabilityName()) + ); + + assumeFalse( + "Tests using INSIST are not supported for now", + testCase.requiredCapabilities.contains(UNMAPPED_FIELDS.capabilityName()) + ); + + assumeFalse( + "Tests using implicit_casting_date_and_date_nanos are not supported for now", + testCase.requiredCapabilities.contains(IMPLICIT_CASTING_DATE_AND_DATE_NANOS.capabilityName()) + ); + + assumeTrue("Cluster needs to support FORK", hasCapabilities(client(), List.of(FORK_V7.capabilityName()))); + } +} diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java index 86051e7e4164d..4860740e7babc 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.compute.operator.DriverProfile; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.parser.ParsingException; import org.junit.Before; @@ -26,13 +27,13 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.getValuesList; import static org.hamcrest.Matchers.equalTo; -// @TestLogging(value = "org.elasticsearch.xpack.esql:TRACE,org.elasticsearch.compute:TRACE", reason = "debug") +@TestLogging(value = "org.elasticsearch.xpack.esql:TRACE,org.elasticsearch.compute:TRACE", reason = "debug") public class ForkIT extends AbstractEsqlIntegTestCase { @Before public void setupIndex() { assumeTrue("requires FORK capability", EsqlCapabilities.Cap.FORK.isEnabled()); - createAndPopulateIndex(); + createAndPopulateIndices(); } public void testSimple() { @@ -706,6 +707,52 @@ public void testWithLookUpAfterFork() { } } + public void testWithUnionTypesBeforeFork() { + var query = """ + FROM test,test-other + | EVAL x = id::keyword + | EVAL id = id::keyword + | EVAL content = content::keyword + | FORK (WHERE x == "2") + (WHERE x == "1") + | SORT _fork, x, content + | KEEP content, id, x, _fork + """; + + try (var resp = run(query)) { + assertColumnNames(resp.columns(), List.of("content", "id", "x", "_fork")); + Iterable> expectedValues = List.of( + List.of("This is a brown dog", "2", "2", "fork1"), + List.of("This is a brown dog", "2", "2", "fork1"), + List.of("This is a brown fox", "1", "1", "fork2"), + List.of("This is a brown fox", "1", "1", "fork2") + ); + assertValues(resp.values(), expectedValues); + } + } + + public void testWithUnionTypesInBranches() { + var query = """ + FROM test,test-other + | EVAL content = content::keyword + | FORK (EVAL x = id::keyword | WHERE x == "2" | EVAL id = x::integer) + (EVAL x = "a" | WHERE id::keyword == "1" | EVAL id = id::integer) + | SORT _fork, x + | KEEP content, id, x, _fork + """; + + try (var resp = run(query)) { + assertColumnNames(resp.columns(), List.of("content", "id", "x", "_fork")); + Iterable> expectedValues = List.of( + List.of("This is a brown dog", 2, "2", "fork1"), + List.of("This is a brown dog", 2, "2", "fork1"), + List.of("This is a brown fox", 1, "a", "fork2"), + List.of("This is a brown fox", 1, "a", "fork2") + ); + assertValues(resp.values(), expectedValues); + } + } + public void testWithEvalWithConflictingTypes() { var query = """ FROM test @@ -833,7 +880,7 @@ public void testProfile() { } } - private void createAndPopulateIndex() { + private void createAndPopulateIndices() { var indexName = "test"; var client = client().admin().indices(); var createRequest = client.prepareCreate(indexName) @@ -867,6 +914,20 @@ private void createAndPopulateIndex() { .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .get(); ensureYellow(lookupIndex); + + var otherTestIndex = "test-other"; + + createRequest = client.prepareCreate(otherTestIndex) + .setSettings(Settings.builder().put("index.number_of_shards", 1)) + .setMapping("id", "type=keyword", "content", "type=keyword"); + assertAcked(createRequest); + client().prepareBulk() + .add(new IndexRequest(otherTestIndex).id("1").source("id", "1", "content", "This is a brown fox")) + .add(new IndexRequest(otherTestIndex).id("2").source("id", "2", "content", "This is a brown dog")) + .add(new IndexRequest(otherTestIndex).id("3").source("id", "3", "content", "This dog is really brown")) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .get(); + ensureYellow(indexName); } static Iterator> valuesFilter(Iterator> values, Predicate> filter) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 494e801eb1f7a..a4fdb108e639d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -1638,20 +1638,21 @@ record TypeResolutionKey(String fieldName, DataType fieldType) {} @Override public LogicalPlan apply(LogicalPlan plan) { unionFieldAttributes = new ArrayList<>(); + return plan.transformUp(LogicalPlan.class, p -> p.childrenResolved() == false ? p : doRule(p)); + } + + private LogicalPlan doRule(LogicalPlan plan) { + Holder alreadyAddedUnionFieldAttributes = new Holder<>(unionFieldAttributes.size()); // Collect field attributes from previous runs - plan.forEachUp(EsRelation.class, rel -> { + if (plan instanceof EsRelation rel) { + unionFieldAttributes.clear(); for (Attribute attr : rel.output()) { if (attr instanceof FieldAttribute fa && fa.field() instanceof MultiTypeEsField && fa.synthetic()) { unionFieldAttributes.add(fa); } } - }); - - return plan.transformUp(LogicalPlan.class, p -> p.childrenResolved() == false ? p : doRule(p)); - } + } - private LogicalPlan doRule(LogicalPlan plan) { - int alreadyAddedUnionFieldAttributes = unionFieldAttributes.size(); // See if the eval function has an unresolved MultiTypeEsField field // Replace the entire convert function with a new FieldAttribute (containing type conversion knowledge) plan = plan.transformExpressionsOnly(e -> { @@ -1660,8 +1661,9 @@ private LogicalPlan doRule(LogicalPlan plan) { } return e; }); + // If no union fields were generated, return the plan as is - if (unionFieldAttributes.size() == alreadyAddedUnionFieldAttributes) { + if (unionFieldAttributes.size() == alreadyAddedUnionFieldAttributes.get()) { return plan; } From f48c38364963cdfa087ec8bfff94643fcbe391fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 10 Jun 2025 17:54:48 +0200 Subject: [PATCH 039/102] ES|QL Completion command syntax change (#129189) --- .../src/main/resources/completion.csv-spec | 4 +- .../esql/src/main/antlr/EsqlBaseLexer.tokens | 240 +- .../esql/src/main/antlr/EsqlBaseParser.g4 | 2 +- .../esql/src/main/antlr/EsqlBaseParser.tokens | 240 +- .../esql/src/main/antlr/lexer/Expression.g4 | 1 - .../esql/src/main/antlr/lexer/Rename.g4 | 2 +- .../xpack/esql/parser/EsqlBaseLexer.interp | 9 +- .../xpack/esql/parser/EsqlBaseLexer.java | 2246 ++++++++--------- .../xpack/esql/parser/EsqlBaseParser.interp | 6 +- .../xpack/esql/parser/EsqlBaseParser.java | 1166 ++++----- .../xpack/esql/analysis/AnalyzerTests.java | 4 +- .../optimizer/LogicalPlanOptimizerTests.java | 2 +- .../PushDownAndCombineFiltersTests.java | 4 +- .../esql/parser/StatementParserTests.java | 8 +- 14 files changed, 1963 insertions(+), 1971 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/completion.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/completion.csv-spec index bbb0278f2b021..9f0cf627eb927 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/completion.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/completion.csv-spec @@ -6,7 +6,7 @@ completion using a ROW source operator required_capability: completion ROW prompt="Who is Victor Hugo?" -| COMPLETION prompt WITH test_completion AS completion_output +| COMPLETION completion_output = prompt WITH test_completion ; prompt:keyword | completion_output:keyword @@ -18,7 +18,7 @@ completion using a ROW source operator and prompt is a multi-valued field required_capability: completion ROW prompt=["Answer the following question:", "Who is Victor Hugo?"] -| COMPLETION prompt WITH test_completion AS completion_output +| COMPLETION completion_output = prompt WITH test_completion ; prompt:keyword | completion_output:keyword diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens b/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens index 72217c4cb3062..8135cc7be8e75 100644 --- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens +++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.tokens @@ -54,82 +54,82 @@ QUOTED_STRING=53 INTEGER_LITERAL=54 DECIMAL_LITERAL=55 AND=56 -AS=57 -ASC=58 -ASSIGN=59 -BY=60 -CAST_OP=61 -COLON=62 -COMMA=63 -DESC=64 -DOT=65 -FALSE=66 -FIRST=67 -IN=68 -IS=69 -LAST=70 -LIKE=71 -NOT=72 -NULL=73 -NULLS=74 -ON=75 -OR=76 -PARAM=77 -RLIKE=78 -TRUE=79 -WITH=80 -EQ=81 -CIEQ=82 -NEQ=83 -LT=84 -LTE=85 -GT=86 -GTE=87 -PLUS=88 -MINUS=89 -ASTERISK=90 -SLASH=91 -PERCENT=92 -LEFT_BRACES=93 -RIGHT_BRACES=94 -DOUBLE_PARAMS=95 -NAMED_OR_POSITIONAL_PARAM=96 -NAMED_OR_POSITIONAL_DOUBLE_PARAMS=97 -OPENING_BRACKET=98 -CLOSING_BRACKET=99 -LP=100 -RP=101 -UNQUOTED_IDENTIFIER=102 -QUOTED_IDENTIFIER=103 -EXPR_LINE_COMMENT=104 -EXPR_MULTILINE_COMMENT=105 -EXPR_WS=106 -METADATA=107 -UNQUOTED_SOURCE=108 -FROM_LINE_COMMENT=109 -FROM_MULTILINE_COMMENT=110 -FROM_WS=111 -FORK_WS=112 -FORK_LINE_COMMENT=113 -FORK_MULTILINE_COMMENT=114 -JOIN=115 -USING=116 -JOIN_LINE_COMMENT=117 -JOIN_MULTILINE_COMMENT=118 -JOIN_WS=119 -LOOKUP_LINE_COMMENT=120 -LOOKUP_MULTILINE_COMMENT=121 -LOOKUP_WS=122 -LOOKUP_FIELD_LINE_COMMENT=123 -LOOKUP_FIELD_MULTILINE_COMMENT=124 -LOOKUP_FIELD_WS=125 -MVEXPAND_LINE_COMMENT=126 -MVEXPAND_MULTILINE_COMMENT=127 -MVEXPAND_WS=128 -ID_PATTERN=129 -PROJECT_LINE_COMMENT=130 -PROJECT_MULTILINE_COMMENT=131 -PROJECT_WS=132 +ASC=57 +ASSIGN=58 +BY=59 +CAST_OP=60 +COLON=61 +COMMA=62 +DESC=63 +DOT=64 +FALSE=65 +FIRST=66 +IN=67 +IS=68 +LAST=69 +LIKE=70 +NOT=71 +NULL=72 +NULLS=73 +ON=74 +OR=75 +PARAM=76 +RLIKE=77 +TRUE=78 +WITH=79 +EQ=80 +CIEQ=81 +NEQ=82 +LT=83 +LTE=84 +GT=85 +GTE=86 +PLUS=87 +MINUS=88 +ASTERISK=89 +SLASH=90 +PERCENT=91 +LEFT_BRACES=92 +RIGHT_BRACES=93 +DOUBLE_PARAMS=94 +NAMED_OR_POSITIONAL_PARAM=95 +NAMED_OR_POSITIONAL_DOUBLE_PARAMS=96 +OPENING_BRACKET=97 +CLOSING_BRACKET=98 +LP=99 +RP=100 +UNQUOTED_IDENTIFIER=101 +QUOTED_IDENTIFIER=102 +EXPR_LINE_COMMENT=103 +EXPR_MULTILINE_COMMENT=104 +EXPR_WS=105 +METADATA=106 +UNQUOTED_SOURCE=107 +FROM_LINE_COMMENT=108 +FROM_MULTILINE_COMMENT=109 +FROM_WS=110 +FORK_WS=111 +FORK_LINE_COMMENT=112 +FORK_MULTILINE_COMMENT=113 +JOIN=114 +USING=115 +JOIN_LINE_COMMENT=116 +JOIN_MULTILINE_COMMENT=117 +JOIN_WS=118 +LOOKUP_LINE_COMMENT=119 +LOOKUP_MULTILINE_COMMENT=120 +LOOKUP_WS=121 +LOOKUP_FIELD_LINE_COMMENT=122 +LOOKUP_FIELD_MULTILINE_COMMENT=123 +LOOKUP_FIELD_WS=124 +MVEXPAND_LINE_COMMENT=125 +MVEXPAND_MULTILINE_COMMENT=126 +MVEXPAND_WS=127 +ID_PATTERN=128 +PROJECT_LINE_COMMENT=129 +PROJECT_MULTILINE_COMMENT=130 +PROJECT_WS=131 +AS=132 RENAME_LINE_COMMENT=133 RENAME_MULTILINE_COMMENT=134 RENAME_WS=135 @@ -158,48 +158,48 @@ SHOW_WS=139 'show'=33 '|'=52 'and'=56 -'as'=57 -'asc'=58 -'='=59 -'by'=60 -'::'=61 -':'=62 -','=63 -'desc'=64 -'.'=65 -'false'=66 -'first'=67 -'in'=68 -'is'=69 -'last'=70 -'like'=71 -'not'=72 -'null'=73 -'nulls'=74 -'on'=75 -'or'=76 -'?'=77 -'rlike'=78 -'true'=79 -'with'=80 -'=='=81 -'=~'=82 -'!='=83 -'<'=84 -'<='=85 -'>'=86 -'>='=87 -'+'=88 -'-'=89 -'*'=90 -'/'=91 -'%'=92 -'{'=93 -'}'=94 -'??'=95 -']'=99 -')'=101 -'metadata'=107 -'join'=115 -'USING'=116 +'asc'=57 +'='=58 +'by'=59 +'::'=60 +':'=61 +','=62 +'desc'=63 +'.'=64 +'false'=65 +'first'=66 +'in'=67 +'is'=68 +'last'=69 +'like'=70 +'not'=71 +'null'=72 +'nulls'=73 +'on'=74 +'or'=75 +'?'=76 +'rlike'=77 +'true'=78 +'with'=79 +'=='=80 +'=~'=81 +'!='=82 +'<'=83 +'<='=84 +'>'=85 +'>='=86 +'+'=87 +'-'=88 +'*'=89 +'/'=90 +'%'=91 +'{'=92 +'}'=93 +'??'=94 +']'=98 +')'=100 +'metadata'=106 +'join'=114 +'USING'=115 +'as'=132 'info'=136 diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 index 974533fcff9bc..bb346cf60550a 100644 --- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 +++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 @@ -308,7 +308,7 @@ rerankCommand ; completionCommand - : COMPLETION prompt=primaryExpression WITH inferenceId=identifierOrParameter (AS targetField=qualifiedName)? + : COMPLETION (targetField=qualifiedName ASSIGN)? prompt=primaryExpression WITH inferenceId=identifierOrParameter ; sampleCommand diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens index 72217c4cb3062..8135cc7be8e75 100644 --- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens +++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.tokens @@ -54,82 +54,82 @@ QUOTED_STRING=53 INTEGER_LITERAL=54 DECIMAL_LITERAL=55 AND=56 -AS=57 -ASC=58 -ASSIGN=59 -BY=60 -CAST_OP=61 -COLON=62 -COMMA=63 -DESC=64 -DOT=65 -FALSE=66 -FIRST=67 -IN=68 -IS=69 -LAST=70 -LIKE=71 -NOT=72 -NULL=73 -NULLS=74 -ON=75 -OR=76 -PARAM=77 -RLIKE=78 -TRUE=79 -WITH=80 -EQ=81 -CIEQ=82 -NEQ=83 -LT=84 -LTE=85 -GT=86 -GTE=87 -PLUS=88 -MINUS=89 -ASTERISK=90 -SLASH=91 -PERCENT=92 -LEFT_BRACES=93 -RIGHT_BRACES=94 -DOUBLE_PARAMS=95 -NAMED_OR_POSITIONAL_PARAM=96 -NAMED_OR_POSITIONAL_DOUBLE_PARAMS=97 -OPENING_BRACKET=98 -CLOSING_BRACKET=99 -LP=100 -RP=101 -UNQUOTED_IDENTIFIER=102 -QUOTED_IDENTIFIER=103 -EXPR_LINE_COMMENT=104 -EXPR_MULTILINE_COMMENT=105 -EXPR_WS=106 -METADATA=107 -UNQUOTED_SOURCE=108 -FROM_LINE_COMMENT=109 -FROM_MULTILINE_COMMENT=110 -FROM_WS=111 -FORK_WS=112 -FORK_LINE_COMMENT=113 -FORK_MULTILINE_COMMENT=114 -JOIN=115 -USING=116 -JOIN_LINE_COMMENT=117 -JOIN_MULTILINE_COMMENT=118 -JOIN_WS=119 -LOOKUP_LINE_COMMENT=120 -LOOKUP_MULTILINE_COMMENT=121 -LOOKUP_WS=122 -LOOKUP_FIELD_LINE_COMMENT=123 -LOOKUP_FIELD_MULTILINE_COMMENT=124 -LOOKUP_FIELD_WS=125 -MVEXPAND_LINE_COMMENT=126 -MVEXPAND_MULTILINE_COMMENT=127 -MVEXPAND_WS=128 -ID_PATTERN=129 -PROJECT_LINE_COMMENT=130 -PROJECT_MULTILINE_COMMENT=131 -PROJECT_WS=132 +ASC=57 +ASSIGN=58 +BY=59 +CAST_OP=60 +COLON=61 +COMMA=62 +DESC=63 +DOT=64 +FALSE=65 +FIRST=66 +IN=67 +IS=68 +LAST=69 +LIKE=70 +NOT=71 +NULL=72 +NULLS=73 +ON=74 +OR=75 +PARAM=76 +RLIKE=77 +TRUE=78 +WITH=79 +EQ=80 +CIEQ=81 +NEQ=82 +LT=83 +LTE=84 +GT=85 +GTE=86 +PLUS=87 +MINUS=88 +ASTERISK=89 +SLASH=90 +PERCENT=91 +LEFT_BRACES=92 +RIGHT_BRACES=93 +DOUBLE_PARAMS=94 +NAMED_OR_POSITIONAL_PARAM=95 +NAMED_OR_POSITIONAL_DOUBLE_PARAMS=96 +OPENING_BRACKET=97 +CLOSING_BRACKET=98 +LP=99 +RP=100 +UNQUOTED_IDENTIFIER=101 +QUOTED_IDENTIFIER=102 +EXPR_LINE_COMMENT=103 +EXPR_MULTILINE_COMMENT=104 +EXPR_WS=105 +METADATA=106 +UNQUOTED_SOURCE=107 +FROM_LINE_COMMENT=108 +FROM_MULTILINE_COMMENT=109 +FROM_WS=110 +FORK_WS=111 +FORK_LINE_COMMENT=112 +FORK_MULTILINE_COMMENT=113 +JOIN=114 +USING=115 +JOIN_LINE_COMMENT=116 +JOIN_MULTILINE_COMMENT=117 +JOIN_WS=118 +LOOKUP_LINE_COMMENT=119 +LOOKUP_MULTILINE_COMMENT=120 +LOOKUP_WS=121 +LOOKUP_FIELD_LINE_COMMENT=122 +LOOKUP_FIELD_MULTILINE_COMMENT=123 +LOOKUP_FIELD_WS=124 +MVEXPAND_LINE_COMMENT=125 +MVEXPAND_MULTILINE_COMMENT=126 +MVEXPAND_WS=127 +ID_PATTERN=128 +PROJECT_LINE_COMMENT=129 +PROJECT_MULTILINE_COMMENT=130 +PROJECT_WS=131 +AS=132 RENAME_LINE_COMMENT=133 RENAME_MULTILINE_COMMENT=134 RENAME_WS=135 @@ -158,48 +158,48 @@ SHOW_WS=139 'show'=33 '|'=52 'and'=56 -'as'=57 -'asc'=58 -'='=59 -'by'=60 -'::'=61 -':'=62 -','=63 -'desc'=64 -'.'=65 -'false'=66 -'first'=67 -'in'=68 -'is'=69 -'last'=70 -'like'=71 -'not'=72 -'null'=73 -'nulls'=74 -'on'=75 -'or'=76 -'?'=77 -'rlike'=78 -'true'=79 -'with'=80 -'=='=81 -'=~'=82 -'!='=83 -'<'=84 -'<='=85 -'>'=86 -'>='=87 -'+'=88 -'-'=89 -'*'=90 -'/'=91 -'%'=92 -'{'=93 -'}'=94 -'??'=95 -']'=99 -')'=101 -'metadata'=107 -'join'=115 -'USING'=116 +'asc'=57 +'='=58 +'by'=59 +'::'=60 +':'=61 +','=62 +'desc'=63 +'.'=64 +'false'=65 +'first'=66 +'in'=67 +'is'=68 +'last'=69 +'like'=70 +'not'=71 +'null'=72 +'nulls'=73 +'on'=74 +'or'=75 +'?'=76 +'rlike'=77 +'true'=78 +'with'=79 +'=='=80 +'=~'=81 +'!='=82 +'<'=83 +'<='=84 +'>'=85 +'>='=86 +'+'=87 +'-'=88 +'*'=89 +'/'=90 +'%'=91 +'{'=92 +'}'=93 +'??'=94 +']'=98 +')'=100 +'metadata'=106 +'join'=114 +'USING'=115 +'as'=132 'info'=136 diff --git a/x-pack/plugin/esql/src/main/antlr/lexer/Expression.g4 b/x-pack/plugin/esql/src/main/antlr/lexer/Expression.g4 index fe233ca949795..13b6233ab5dee 100644 --- a/x-pack/plugin/esql/src/main/antlr/lexer/Expression.g4 +++ b/x-pack/plugin/esql/src/main/antlr/lexer/Expression.g4 @@ -87,7 +87,6 @@ DECIMAL_LITERAL AND : 'and'; -AS: 'as'; ASC : 'asc'; ASSIGN : '='; BY : 'by'; diff --git a/x-pack/plugin/esql/src/main/antlr/lexer/Rename.g4 b/x-pack/plugin/esql/src/main/antlr/lexer/Rename.g4 index 09b58fc55259b..0d378e5e59dd0 100644 --- a/x-pack/plugin/esql/src/main/antlr/lexer/Rename.g4 +++ b/x-pack/plugin/esql/src/main/antlr/lexer/Rename.g4 @@ -22,7 +22,7 @@ RENAME_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_PO RENAME_DOUBLE_PARAMS : DOUBLE_PARAMS -> type(DOUBLE_PARAMS); RENAME_NAMED_OR_POSITIONAL_DOUBLE_PARAMS : NAMED_OR_POSITIONAL_DOUBLE_PARAMS -> type(NAMED_OR_POSITIONAL_DOUBLE_PARAMS); -RENAME_AS : AS -> type(AS); +AS: 'as'; RENAME_ID_PATTERN : ID_PATTERN -> type(ID_PATTERN) 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 9eb7c41164a0f..944d1424ab0a3 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 @@ -56,7 +56,6 @@ null null null 'and' -'as' 'asc' '=' 'by' @@ -132,6 +131,7 @@ null null null null +'as' null null null @@ -198,7 +198,6 @@ QUOTED_STRING INTEGER_LITERAL DECIMAL_LITERAL AND -AS ASC ASSIGN BY @@ -274,6 +273,7 @@ ID_PATTERN PROJECT_LINE_COMMENT PROJECT_MULTILINE_COMMENT PROJECT_WS +AS RENAME_LINE_COMMENT RENAME_MULTILINE_COMMENT RENAME_WS @@ -377,7 +377,6 @@ QUOTED_STRING INTEGER_LITERAL DECIMAL_LITERAL AND -AS ASC ASSIGN BY @@ -511,7 +510,7 @@ RENAME_PARAM RENAME_NAMED_OR_POSITIONAL_PARAM RENAME_DOUBLE_PARAMS RENAME_NAMED_OR_POSITIONAL_DOUBLE_PARAMS -RENAME_AS +AS RENAME_ID_PATTERN RENAME_LINE_COMMENT RENAME_MULTILINE_COMMENT @@ -545,4 +544,4 @@ RENAME_MODE SHOW_MODE atn: -[4, 0, 139, 1782, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 2, 199, 7, 199, 2, 200, 7, 200, 2, 201, 7, 201, 2, 202, 7, 202, 2, 203, 7, 203, 2, 204, 7, 204, 2, 205, 7, 205, 2, 206, 7, 206, 2, 207, 7, 207, 2, 208, 7, 208, 2, 209, 7, 209, 2, 210, 7, 210, 2, 211, 7, 211, 2, 212, 7, 212, 2, 213, 7, 213, 2, 214, 7, 214, 2, 215, 7, 215, 2, 216, 7, 216, 2, 217, 7, 217, 2, 218, 7, 218, 2, 219, 7, 219, 2, 220, 7, 220, 2, 221, 7, 221, 2, 222, 7, 222, 2, 223, 7, 223, 2, 224, 7, 224, 2, 225, 7, 225, 2, 226, 7, 226, 2, 227, 7, 227, 2, 228, 7, 228, 2, 229, 7, 229, 2, 230, 7, 230, 2, 231, 7, 231, 2, 232, 7, 232, 2, 233, 7, 233, 2, 234, 7, 234, 2, 235, 7, 235, 2, 236, 7, 236, 2, 237, 7, 237, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 497, 8, 0, 10, 0, 12, 0, 500, 9, 0, 1, 0, 3, 0, 503, 8, 0, 1, 0, 3, 0, 506, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 515, 8, 1, 10, 1, 12, 1, 518, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 526, 8, 2, 11, 2, 12, 2, 527, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 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, 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, 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, 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, 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, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 4, 33, 804, 8, 33, 11, 33, 12, 33, 805, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 4, 49, 874, 8, 49, 11, 49, 12, 49, 875, 1, 49, 1, 49, 3, 49, 880, 8, 49, 1, 49, 4, 49, 883, 8, 49, 11, 49, 12, 49, 884, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 4, 70, 975, 8, 70, 11, 70, 12, 70, 976, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 84, 1, 84, 3, 84, 1028, 8, 84, 1, 84, 4, 84, 1031, 8, 84, 11, 84, 12, 84, 1032, 1, 85, 1, 85, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 3, 87, 1042, 8, 87, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 3, 89, 1049, 8, 89, 1, 90, 1, 90, 1, 90, 5, 90, 1054, 8, 90, 10, 90, 12, 90, 1057, 9, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 5, 90, 1065, 8, 90, 10, 90, 12, 90, 1068, 9, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 3, 90, 1075, 8, 90, 1, 90, 3, 90, 1078, 8, 90, 3, 90, 1080, 8, 90, 1, 91, 4, 91, 1083, 8, 91, 11, 91, 12, 91, 1084, 1, 92, 4, 92, 1088, 8, 92, 11, 92, 12, 92, 1089, 1, 92, 1, 92, 5, 92, 1094, 8, 92, 10, 92, 12, 92, 1097, 9, 92, 1, 92, 1, 92, 4, 92, 1101, 8, 92, 11, 92, 12, 92, 1102, 1, 92, 4, 92, 1106, 8, 92, 11, 92, 12, 92, 1107, 1, 92, 1, 92, 5, 92, 1112, 8, 92, 10, 92, 12, 92, 1115, 9, 92, 3, 92, 1117, 8, 92, 1, 92, 1, 92, 1, 92, 1, 92, 4, 92, 1123, 8, 92, 11, 92, 12, 92, 1124, 1, 92, 1, 92, 3, 92, 1129, 8, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 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, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 126, 1, 126, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 1, 130, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 3, 134, 1271, 8, 134, 1, 134, 5, 134, 1274, 8, 134, 10, 134, 12, 134, 1277, 9, 134, 1, 134, 1, 134, 4, 134, 1281, 8, 134, 11, 134, 12, 134, 1282, 3, 134, 1285, 8, 134, 1, 135, 1, 135, 1, 135, 3, 135, 1290, 8, 135, 1, 135, 5, 135, 1293, 8, 135, 10, 135, 12, 135, 1296, 9, 135, 1, 135, 1, 135, 4, 135, 1300, 8, 135, 11, 135, 12, 135, 1301, 3, 135, 1304, 8, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 5, 140, 1328, 8, 140, 10, 140, 12, 140, 1331, 9, 140, 1, 140, 1, 140, 3, 140, 1335, 8, 140, 1, 140, 4, 140, 1338, 8, 140, 11, 140, 12, 140, 1339, 3, 140, 1342, 8, 140, 1, 141, 1, 141, 4, 141, 1346, 8, 141, 11, 141, 12, 141, 1347, 1, 141, 1, 141, 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, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 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, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 3, 154, 1407, 8, 154, 1, 155, 4, 155, 1410, 8, 155, 11, 155, 12, 155, 1411, 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, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 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, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 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, 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, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 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, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 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, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 199, 1, 199, 1, 199, 1, 199, 1, 200, 1, 200, 1, 200, 1, 200, 1, 201, 1, 201, 1, 201, 1, 201, 1, 202, 1, 202, 1, 202, 1, 202, 1, 203, 1, 203, 1, 203, 1, 203, 1, 204, 1, 204, 1, 204, 1, 204, 1, 205, 1, 205, 1, 205, 1, 205, 1, 206, 1, 206, 1, 206, 1, 206, 1, 207, 1, 207, 1, 207, 1, 207, 1, 207, 1, 208, 1, 208, 1, 208, 1, 208, 1, 209, 1, 209, 1, 209, 1, 209, 1, 210, 1, 210, 1, 210, 1, 210, 1, 211, 1, 211, 1, 211, 1, 211, 1, 212, 1, 212, 1, 212, 1, 212, 1, 213, 1, 213, 1, 213, 1, 213, 1, 214, 1, 214, 1, 214, 1, 214, 3, 214, 1667, 8, 214, 1, 215, 1, 215, 3, 215, 1671, 8, 215, 1, 215, 5, 215, 1674, 8, 215, 10, 215, 12, 215, 1677, 9, 215, 1, 215, 1, 215, 3, 215, 1681, 8, 215, 1, 215, 4, 215, 1684, 8, 215, 11, 215, 12, 215, 1685, 3, 215, 1688, 8, 215, 1, 216, 1, 216, 4, 216, 1692, 8, 216, 11, 216, 12, 216, 1693, 1, 217, 1, 217, 1, 217, 1, 217, 1, 218, 1, 218, 1, 218, 1, 218, 1, 219, 1, 219, 1, 219, 1, 219, 1, 220, 1, 220, 1, 220, 1, 220, 1, 220, 1, 221, 1, 221, 1, 221, 1, 221, 1, 222, 1, 222, 1, 222, 1, 222, 1, 223, 1, 223, 1, 223, 1, 223, 1, 224, 1, 224, 1, 224, 1, 224, 1, 225, 1, 225, 1, 225, 1, 225, 1, 226, 1, 226, 1, 226, 1, 226, 1, 227, 1, 227, 1, 227, 1, 227, 1, 228, 1, 228, 1, 228, 1, 228, 1, 229, 1, 229, 1, 229, 1, 229, 1, 230, 1, 230, 1, 230, 1, 230, 1, 231, 1, 231, 1, 231, 1, 231, 1, 232, 1, 232, 1, 232, 1, 232, 1, 233, 1, 233, 1, 233, 1, 233, 1, 233, 1, 234, 1, 234, 1, 234, 1, 234, 1, 234, 1, 235, 1, 235, 1, 235, 1, 235, 1, 236, 1, 236, 1, 236, 1, 236, 1, 237, 1, 237, 1, 237, 1, 237, 2, 516, 1066, 0, 238, 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, 27, 70, 28, 72, 29, 74, 30, 76, 31, 78, 32, 80, 33, 82, 34, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 0, 98, 35, 100, 36, 102, 37, 104, 0, 106, 0, 108, 0, 110, 0, 112, 0, 114, 38, 116, 0, 118, 39, 120, 40, 122, 41, 124, 0, 126, 0, 128, 0, 130, 0, 132, 0, 134, 0, 136, 0, 138, 0, 140, 0, 142, 0, 144, 0, 146, 42, 148, 43, 150, 44, 152, 0, 154, 0, 156, 45, 158, 46, 160, 47, 162, 48, 164, 0, 166, 0, 168, 49, 170, 50, 172, 51, 174, 52, 176, 0, 178, 0, 180, 0, 182, 0, 184, 0, 186, 0, 188, 0, 190, 0, 192, 0, 194, 0, 196, 53, 198, 54, 200, 55, 202, 56, 204, 57, 206, 58, 208, 59, 210, 60, 212, 61, 214, 62, 216, 63, 218, 64, 220, 65, 222, 66, 224, 67, 226, 68, 228, 69, 230, 70, 232, 71, 234, 72, 236, 73, 238, 74, 240, 75, 242, 76, 244, 77, 246, 78, 248, 79, 250, 80, 252, 81, 254, 82, 256, 83, 258, 84, 260, 85, 262, 86, 264, 87, 266, 88, 268, 89, 270, 90, 272, 91, 274, 92, 276, 93, 278, 94, 280, 95, 282, 0, 284, 96, 286, 97, 288, 98, 290, 99, 292, 100, 294, 101, 296, 102, 298, 0, 300, 103, 302, 104, 304, 105, 306, 106, 308, 0, 310, 0, 312, 0, 314, 0, 316, 0, 318, 0, 320, 0, 322, 107, 324, 0, 326, 108, 328, 0, 330, 0, 332, 109, 334, 110, 336, 111, 338, 0, 340, 0, 342, 112, 344, 113, 346, 114, 348, 0, 350, 115, 352, 0, 354, 0, 356, 116, 358, 0, 360, 0, 362, 0, 364, 0, 366, 0, 368, 117, 370, 118, 372, 119, 374, 0, 376, 0, 378, 0, 380, 0, 382, 0, 384, 0, 386, 0, 388, 120, 390, 121, 392, 122, 394, 0, 396, 0, 398, 0, 400, 0, 402, 123, 404, 124, 406, 125, 408, 0, 410, 0, 412, 0, 414, 0, 416, 0, 418, 0, 420, 0, 422, 0, 424, 126, 426, 127, 428, 128, 430, 0, 432, 0, 434, 0, 436, 0, 438, 0, 440, 0, 442, 0, 444, 0, 446, 0, 448, 129, 450, 130, 452, 131, 454, 132, 456, 0, 458, 0, 460, 0, 462, 0, 464, 0, 466, 0, 468, 0, 470, 0, 472, 0, 474, 0, 476, 133, 478, 134, 480, 135, 482, 0, 484, 136, 486, 137, 488, 138, 490, 139, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 36, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 65, 65, 97, 97, 2, 0, 78, 78, 110, 110, 2, 0, 71, 71, 103, 103, 2, 0, 69, 69, 101, 101, 2, 0, 80, 80, 112, 112, 2, 0, 79, 79, 111, 111, 2, 0, 73, 73, 105, 105, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 88, 88, 120, 120, 2, 0, 76, 76, 108, 108, 2, 0, 77, 77, 109, 109, 2, 0, 68, 68, 100, 100, 2, 0, 83, 83, 115, 115, 2, 0, 86, 86, 118, 118, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 70, 70, 102, 102, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 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, 2, 0, 74, 74, 106, 106, 1813, 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, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 78, 1, 0, 0, 0, 0, 80, 1, 0, 0, 0, 0, 82, 1, 0, 0, 0, 1, 84, 1, 0, 0, 0, 1, 86, 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, 2, 104, 1, 0, 0, 0, 2, 106, 1, 0, 0, 0, 2, 108, 1, 0, 0, 0, 2, 110, 1, 0, 0, 0, 2, 114, 1, 0, 0, 0, 2, 116, 1, 0, 0, 0, 2, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 3, 124, 1, 0, 0, 0, 3, 126, 1, 0, 0, 0, 3, 128, 1, 0, 0, 0, 3, 130, 1, 0, 0, 0, 3, 132, 1, 0, 0, 0, 3, 134, 1, 0, 0, 0, 3, 136, 1, 0, 0, 0, 3, 138, 1, 0, 0, 0, 3, 140, 1, 0, 0, 0, 3, 142, 1, 0, 0, 0, 3, 144, 1, 0, 0, 0, 3, 146, 1, 0, 0, 0, 3, 148, 1, 0, 0, 0, 3, 150, 1, 0, 0, 0, 4, 152, 1, 0, 0, 0, 4, 154, 1, 0, 0, 0, 4, 156, 1, 0, 0, 0, 4, 158, 1, 0, 0, 0, 4, 160, 1, 0, 0, 0, 4, 162, 1, 0, 0, 0, 5, 164, 1, 0, 0, 0, 5, 166, 1, 0, 0, 0, 5, 168, 1, 0, 0, 0, 5, 170, 1, 0, 0, 0, 5, 172, 1, 0, 0, 0, 6, 174, 1, 0, 0, 0, 6, 196, 1, 0, 0, 0, 6, 198, 1, 0, 0, 0, 6, 200, 1, 0, 0, 0, 6, 202, 1, 0, 0, 0, 6, 204, 1, 0, 0, 0, 6, 206, 1, 0, 0, 0, 6, 208, 1, 0, 0, 0, 6, 210, 1, 0, 0, 0, 6, 212, 1, 0, 0, 0, 6, 214, 1, 0, 0, 0, 6, 216, 1, 0, 0, 0, 6, 218, 1, 0, 0, 0, 6, 220, 1, 0, 0, 0, 6, 222, 1, 0, 0, 0, 6, 224, 1, 0, 0, 0, 6, 226, 1, 0, 0, 0, 6, 228, 1, 0, 0, 0, 6, 230, 1, 0, 0, 0, 6, 232, 1, 0, 0, 0, 6, 234, 1, 0, 0, 0, 6, 236, 1, 0, 0, 0, 6, 238, 1, 0, 0, 0, 6, 240, 1, 0, 0, 0, 6, 242, 1, 0, 0, 0, 6, 244, 1, 0, 0, 0, 6, 246, 1, 0, 0, 0, 6, 248, 1, 0, 0, 0, 6, 250, 1, 0, 0, 0, 6, 252, 1, 0, 0, 0, 6, 254, 1, 0, 0, 0, 6, 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, 266, 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, 6, 278, 1, 0, 0, 0, 6, 280, 1, 0, 0, 0, 6, 282, 1, 0, 0, 0, 6, 284, 1, 0, 0, 0, 6, 286, 1, 0, 0, 0, 6, 288, 1, 0, 0, 0, 6, 290, 1, 0, 0, 0, 6, 292, 1, 0, 0, 0, 6, 294, 1, 0, 0, 0, 6, 296, 1, 0, 0, 0, 6, 300, 1, 0, 0, 0, 6, 302, 1, 0, 0, 0, 6, 304, 1, 0, 0, 0, 6, 306, 1, 0, 0, 0, 7, 308, 1, 0, 0, 0, 7, 310, 1, 0, 0, 0, 7, 312, 1, 0, 0, 0, 7, 314, 1, 0, 0, 0, 7, 316, 1, 0, 0, 0, 7, 318, 1, 0, 0, 0, 7, 320, 1, 0, 0, 0, 7, 322, 1, 0, 0, 0, 7, 326, 1, 0, 0, 0, 7, 328, 1, 0, 0, 0, 7, 330, 1, 0, 0, 0, 7, 332, 1, 0, 0, 0, 7, 334, 1, 0, 0, 0, 7, 336, 1, 0, 0, 0, 8, 338, 1, 0, 0, 0, 8, 340, 1, 0, 0, 0, 8, 342, 1, 0, 0, 0, 8, 344, 1, 0, 0, 0, 8, 346, 1, 0, 0, 0, 9, 348, 1, 0, 0, 0, 9, 350, 1, 0, 0, 0, 9, 352, 1, 0, 0, 0, 9, 354, 1, 0, 0, 0, 9, 356, 1, 0, 0, 0, 9, 358, 1, 0, 0, 0, 9, 360, 1, 0, 0, 0, 9, 362, 1, 0, 0, 0, 9, 364, 1, 0, 0, 0, 9, 366, 1, 0, 0, 0, 9, 368, 1, 0, 0, 0, 9, 370, 1, 0, 0, 0, 9, 372, 1, 0, 0, 0, 10, 374, 1, 0, 0, 0, 10, 376, 1, 0, 0, 0, 10, 378, 1, 0, 0, 0, 10, 380, 1, 0, 0, 0, 10, 382, 1, 0, 0, 0, 10, 384, 1, 0, 0, 0, 10, 386, 1, 0, 0, 0, 10, 388, 1, 0, 0, 0, 10, 390, 1, 0, 0, 0, 10, 392, 1, 0, 0, 0, 11, 394, 1, 0, 0, 0, 11, 396, 1, 0, 0, 0, 11, 398, 1, 0, 0, 0, 11, 400, 1, 0, 0, 0, 11, 402, 1, 0, 0, 0, 11, 404, 1, 0, 0, 0, 11, 406, 1, 0, 0, 0, 12, 408, 1, 0, 0, 0, 12, 410, 1, 0, 0, 0, 12, 412, 1, 0, 0, 0, 12, 414, 1, 0, 0, 0, 12, 416, 1, 0, 0, 0, 12, 418, 1, 0, 0, 0, 12, 420, 1, 0, 0, 0, 12, 422, 1, 0, 0, 0, 12, 424, 1, 0, 0, 0, 12, 426, 1, 0, 0, 0, 12, 428, 1, 0, 0, 0, 13, 430, 1, 0, 0, 0, 13, 432, 1, 0, 0, 0, 13, 434, 1, 0, 0, 0, 13, 436, 1, 0, 0, 0, 13, 438, 1, 0, 0, 0, 13, 440, 1, 0, 0, 0, 13, 442, 1, 0, 0, 0, 13, 448, 1, 0, 0, 0, 13, 450, 1, 0, 0, 0, 13, 452, 1, 0, 0, 0, 13, 454, 1, 0, 0, 0, 14, 456, 1, 0, 0, 0, 14, 458, 1, 0, 0, 0, 14, 460, 1, 0, 0, 0, 14, 462, 1, 0, 0, 0, 14, 464, 1, 0, 0, 0, 14, 466, 1, 0, 0, 0, 14, 468, 1, 0, 0, 0, 14, 470, 1, 0, 0, 0, 14, 472, 1, 0, 0, 0, 14, 474, 1, 0, 0, 0, 14, 476, 1, 0, 0, 0, 14, 478, 1, 0, 0, 0, 14, 480, 1, 0, 0, 0, 15, 482, 1, 0, 0, 0, 15, 484, 1, 0, 0, 0, 15, 486, 1, 0, 0, 0, 15, 488, 1, 0, 0, 0, 15, 490, 1, 0, 0, 0, 16, 492, 1, 0, 0, 0, 18, 509, 1, 0, 0, 0, 20, 525, 1, 0, 0, 0, 22, 531, 1, 0, 0, 0, 24, 546, 1, 0, 0, 0, 26, 555, 1, 0, 0, 0, 28, 565, 1, 0, 0, 0, 30, 578, 1, 0, 0, 0, 32, 588, 1, 0, 0, 0, 34, 595, 1, 0, 0, 0, 36, 602, 1, 0, 0, 0, 38, 610, 1, 0, 0, 0, 40, 616, 1, 0, 0, 0, 42, 623, 1, 0, 0, 0, 44, 631, 1, 0, 0, 0, 46, 639, 1, 0, 0, 0, 48, 654, 1, 0, 0, 0, 50, 664, 1, 0, 0, 0, 52, 674, 1, 0, 0, 0, 54, 681, 1, 0, 0, 0, 56, 687, 1, 0, 0, 0, 58, 695, 1, 0, 0, 0, 60, 704, 1, 0, 0, 0, 62, 712, 1, 0, 0, 0, 64, 720, 1, 0, 0, 0, 66, 729, 1, 0, 0, 0, 68, 741, 1, 0, 0, 0, 70, 753, 1, 0, 0, 0, 72, 760, 1, 0, 0, 0, 74, 767, 1, 0, 0, 0, 76, 779, 1, 0, 0, 0, 78, 786, 1, 0, 0, 0, 80, 795, 1, 0, 0, 0, 82, 803, 1, 0, 0, 0, 84, 809, 1, 0, 0, 0, 86, 814, 1, 0, 0, 0, 88, 818, 1, 0, 0, 0, 90, 822, 1, 0, 0, 0, 92, 826, 1, 0, 0, 0, 94, 830, 1, 0, 0, 0, 96, 834, 1, 0, 0, 0, 98, 838, 1, 0, 0, 0, 100, 842, 1, 0, 0, 0, 102, 846, 1, 0, 0, 0, 104, 850, 1, 0, 0, 0, 106, 855, 1, 0, 0, 0, 108, 860, 1, 0, 0, 0, 110, 865, 1, 0, 0, 0, 112, 870, 1, 0, 0, 0, 114, 879, 1, 0, 0, 0, 116, 886, 1, 0, 0, 0, 118, 890, 1, 0, 0, 0, 120, 894, 1, 0, 0, 0, 122, 898, 1, 0, 0, 0, 124, 902, 1, 0, 0, 0, 126, 908, 1, 0, 0, 0, 128, 912, 1, 0, 0, 0, 130, 916, 1, 0, 0, 0, 132, 920, 1, 0, 0, 0, 134, 924, 1, 0, 0, 0, 136, 928, 1, 0, 0, 0, 138, 932, 1, 0, 0, 0, 140, 936, 1, 0, 0, 0, 142, 940, 1, 0, 0, 0, 144, 944, 1, 0, 0, 0, 146, 948, 1, 0, 0, 0, 148, 952, 1, 0, 0, 0, 150, 956, 1, 0, 0, 0, 152, 960, 1, 0, 0, 0, 154, 965, 1, 0, 0, 0, 156, 974, 1, 0, 0, 0, 158, 978, 1, 0, 0, 0, 160, 982, 1, 0, 0, 0, 162, 986, 1, 0, 0, 0, 164, 990, 1, 0, 0, 0, 166, 995, 1, 0, 0, 0, 168, 1000, 1, 0, 0, 0, 170, 1004, 1, 0, 0, 0, 172, 1008, 1, 0, 0, 0, 174, 1012, 1, 0, 0, 0, 176, 1016, 1, 0, 0, 0, 178, 1018, 1, 0, 0, 0, 180, 1020, 1, 0, 0, 0, 182, 1023, 1, 0, 0, 0, 184, 1025, 1, 0, 0, 0, 186, 1034, 1, 0, 0, 0, 188, 1036, 1, 0, 0, 0, 190, 1041, 1, 0, 0, 0, 192, 1043, 1, 0, 0, 0, 194, 1048, 1, 0, 0, 0, 196, 1079, 1, 0, 0, 0, 198, 1082, 1, 0, 0, 0, 200, 1128, 1, 0, 0, 0, 202, 1130, 1, 0, 0, 0, 204, 1134, 1, 0, 0, 0, 206, 1137, 1, 0, 0, 0, 208, 1141, 1, 0, 0, 0, 210, 1143, 1, 0, 0, 0, 212, 1146, 1, 0, 0, 0, 214, 1149, 1, 0, 0, 0, 216, 1151, 1, 0, 0, 0, 218, 1153, 1, 0, 0, 0, 220, 1158, 1, 0, 0, 0, 222, 1160, 1, 0, 0, 0, 224, 1166, 1, 0, 0, 0, 226, 1172, 1, 0, 0, 0, 228, 1175, 1, 0, 0, 0, 230, 1178, 1, 0, 0, 0, 232, 1183, 1, 0, 0, 0, 234, 1188, 1, 0, 0, 0, 236, 1192, 1, 0, 0, 0, 238, 1197, 1, 0, 0, 0, 240, 1203, 1, 0, 0, 0, 242, 1206, 1, 0, 0, 0, 244, 1209, 1, 0, 0, 0, 246, 1211, 1, 0, 0, 0, 248, 1217, 1, 0, 0, 0, 250, 1222, 1, 0, 0, 0, 252, 1227, 1, 0, 0, 0, 254, 1230, 1, 0, 0, 0, 256, 1233, 1, 0, 0, 0, 258, 1236, 1, 0, 0, 0, 260, 1238, 1, 0, 0, 0, 262, 1241, 1, 0, 0, 0, 264, 1243, 1, 0, 0, 0, 266, 1246, 1, 0, 0, 0, 268, 1248, 1, 0, 0, 0, 270, 1250, 1, 0, 0, 0, 272, 1252, 1, 0, 0, 0, 274, 1254, 1, 0, 0, 0, 276, 1256, 1, 0, 0, 0, 278, 1258, 1, 0, 0, 0, 280, 1260, 1, 0, 0, 0, 282, 1263, 1, 0, 0, 0, 284, 1284, 1, 0, 0, 0, 286, 1303, 1, 0, 0, 0, 288, 1305, 1, 0, 0, 0, 290, 1310, 1, 0, 0, 0, 292, 1315, 1, 0, 0, 0, 294, 1320, 1, 0, 0, 0, 296, 1341, 1, 0, 0, 0, 298, 1343, 1, 0, 0, 0, 300, 1351, 1, 0, 0, 0, 302, 1353, 1, 0, 0, 0, 304, 1357, 1, 0, 0, 0, 306, 1361, 1, 0, 0, 0, 308, 1365, 1, 0, 0, 0, 310, 1370, 1, 0, 0, 0, 312, 1374, 1, 0, 0, 0, 314, 1378, 1, 0, 0, 0, 316, 1382, 1, 0, 0, 0, 318, 1386, 1, 0, 0, 0, 320, 1390, 1, 0, 0, 0, 322, 1394, 1, 0, 0, 0, 324, 1406, 1, 0, 0, 0, 326, 1409, 1, 0, 0, 0, 328, 1413, 1, 0, 0, 0, 330, 1417, 1, 0, 0, 0, 332, 1421, 1, 0, 0, 0, 334, 1425, 1, 0, 0, 0, 336, 1429, 1, 0, 0, 0, 338, 1433, 1, 0, 0, 0, 340, 1438, 1, 0, 0, 0, 342, 1443, 1, 0, 0, 0, 344, 1447, 1, 0, 0, 0, 346, 1451, 1, 0, 0, 0, 348, 1455, 1, 0, 0, 0, 350, 1460, 1, 0, 0, 0, 352, 1465, 1, 0, 0, 0, 354, 1469, 1, 0, 0, 0, 356, 1475, 1, 0, 0, 0, 358, 1484, 1, 0, 0, 0, 360, 1488, 1, 0, 0, 0, 362, 1492, 1, 0, 0, 0, 364, 1496, 1, 0, 0, 0, 366, 1500, 1, 0, 0, 0, 368, 1504, 1, 0, 0, 0, 370, 1508, 1, 0, 0, 0, 372, 1512, 1, 0, 0, 0, 374, 1516, 1, 0, 0, 0, 376, 1521, 1, 0, 0, 0, 378, 1525, 1, 0, 0, 0, 380, 1529, 1, 0, 0, 0, 382, 1533, 1, 0, 0, 0, 384, 1538, 1, 0, 0, 0, 386, 1542, 1, 0, 0, 0, 388, 1546, 1, 0, 0, 0, 390, 1550, 1, 0, 0, 0, 392, 1554, 1, 0, 0, 0, 394, 1558, 1, 0, 0, 0, 396, 1564, 1, 0, 0, 0, 398, 1568, 1, 0, 0, 0, 400, 1572, 1, 0, 0, 0, 402, 1576, 1, 0, 0, 0, 404, 1580, 1, 0, 0, 0, 406, 1584, 1, 0, 0, 0, 408, 1588, 1, 0, 0, 0, 410, 1593, 1, 0, 0, 0, 412, 1597, 1, 0, 0, 0, 414, 1601, 1, 0, 0, 0, 416, 1605, 1, 0, 0, 0, 418, 1609, 1, 0, 0, 0, 420, 1613, 1, 0, 0, 0, 422, 1617, 1, 0, 0, 0, 424, 1621, 1, 0, 0, 0, 426, 1625, 1, 0, 0, 0, 428, 1629, 1, 0, 0, 0, 430, 1633, 1, 0, 0, 0, 432, 1638, 1, 0, 0, 0, 434, 1642, 1, 0, 0, 0, 436, 1646, 1, 0, 0, 0, 438, 1650, 1, 0, 0, 0, 440, 1654, 1, 0, 0, 0, 442, 1658, 1, 0, 0, 0, 444, 1666, 1, 0, 0, 0, 446, 1687, 1, 0, 0, 0, 448, 1691, 1, 0, 0, 0, 450, 1695, 1, 0, 0, 0, 452, 1699, 1, 0, 0, 0, 454, 1703, 1, 0, 0, 0, 456, 1707, 1, 0, 0, 0, 458, 1712, 1, 0, 0, 0, 460, 1716, 1, 0, 0, 0, 462, 1720, 1, 0, 0, 0, 464, 1724, 1, 0, 0, 0, 466, 1728, 1, 0, 0, 0, 468, 1732, 1, 0, 0, 0, 470, 1736, 1, 0, 0, 0, 472, 1740, 1, 0, 0, 0, 474, 1744, 1, 0, 0, 0, 476, 1748, 1, 0, 0, 0, 478, 1752, 1, 0, 0, 0, 480, 1756, 1, 0, 0, 0, 482, 1760, 1, 0, 0, 0, 484, 1765, 1, 0, 0, 0, 486, 1770, 1, 0, 0, 0, 488, 1774, 1, 0, 0, 0, 490, 1778, 1, 0, 0, 0, 492, 493, 5, 47, 0, 0, 493, 494, 5, 47, 0, 0, 494, 498, 1, 0, 0, 0, 495, 497, 8, 0, 0, 0, 496, 495, 1, 0, 0, 0, 497, 500, 1, 0, 0, 0, 498, 496, 1, 0, 0, 0, 498, 499, 1, 0, 0, 0, 499, 502, 1, 0, 0, 0, 500, 498, 1, 0, 0, 0, 501, 503, 5, 13, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 505, 1, 0, 0, 0, 504, 506, 5, 10, 0, 0, 505, 504, 1, 0, 0, 0, 505, 506, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 508, 6, 0, 0, 0, 508, 17, 1, 0, 0, 0, 509, 510, 5, 47, 0, 0, 510, 511, 5, 42, 0, 0, 511, 516, 1, 0, 0, 0, 512, 515, 3, 18, 1, 0, 513, 515, 9, 0, 0, 0, 514, 512, 1, 0, 0, 0, 514, 513, 1, 0, 0, 0, 515, 518, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 516, 514, 1, 0, 0, 0, 517, 519, 1, 0, 0, 0, 518, 516, 1, 0, 0, 0, 519, 520, 5, 42, 0, 0, 520, 521, 5, 47, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, 6, 1, 0, 0, 523, 19, 1, 0, 0, 0, 524, 526, 7, 1, 0, 0, 525, 524, 1, 0, 0, 0, 526, 527, 1, 0, 0, 0, 527, 525, 1, 0, 0, 0, 527, 528, 1, 0, 0, 0, 528, 529, 1, 0, 0, 0, 529, 530, 6, 2, 0, 0, 530, 21, 1, 0, 0, 0, 531, 532, 7, 2, 0, 0, 532, 533, 7, 3, 0, 0, 533, 534, 7, 4, 0, 0, 534, 535, 7, 5, 0, 0, 535, 536, 7, 6, 0, 0, 536, 537, 7, 7, 0, 0, 537, 538, 5, 95, 0, 0, 538, 539, 7, 8, 0, 0, 539, 540, 7, 9, 0, 0, 540, 541, 7, 10, 0, 0, 541, 542, 7, 5, 0, 0, 542, 543, 7, 11, 0, 0, 543, 544, 1, 0, 0, 0, 544, 545, 6, 3, 1, 0, 545, 23, 1, 0, 0, 0, 546, 547, 7, 7, 0, 0, 547, 548, 7, 5, 0, 0, 548, 549, 7, 12, 0, 0, 549, 550, 7, 10, 0, 0, 550, 551, 7, 2, 0, 0, 551, 552, 7, 3, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 6, 4, 2, 0, 554, 25, 1, 0, 0, 0, 555, 556, 7, 7, 0, 0, 556, 557, 7, 13, 0, 0, 557, 558, 7, 8, 0, 0, 558, 559, 7, 14, 0, 0, 559, 560, 7, 4, 0, 0, 560, 561, 7, 10, 0, 0, 561, 562, 7, 5, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 6, 5, 3, 0, 564, 27, 1, 0, 0, 0, 565, 566, 7, 2, 0, 0, 566, 567, 7, 9, 0, 0, 567, 568, 7, 15, 0, 0, 568, 569, 7, 8, 0, 0, 569, 570, 7, 14, 0, 0, 570, 571, 7, 7, 0, 0, 571, 572, 7, 11, 0, 0, 572, 573, 7, 10, 0, 0, 573, 574, 7, 9, 0, 0, 574, 575, 7, 5, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 6, 6, 4, 0, 577, 29, 1, 0, 0, 0, 578, 579, 7, 16, 0, 0, 579, 580, 7, 10, 0, 0, 580, 581, 7, 17, 0, 0, 581, 582, 7, 17, 0, 0, 582, 583, 7, 7, 0, 0, 583, 584, 7, 2, 0, 0, 584, 585, 7, 11, 0, 0, 585, 586, 1, 0, 0, 0, 586, 587, 6, 7, 4, 0, 587, 31, 1, 0, 0, 0, 588, 589, 7, 7, 0, 0, 589, 590, 7, 18, 0, 0, 590, 591, 7, 4, 0, 0, 591, 592, 7, 14, 0, 0, 592, 593, 1, 0, 0, 0, 593, 594, 6, 8, 4, 0, 594, 33, 1, 0, 0, 0, 595, 596, 7, 6, 0, 0, 596, 597, 7, 12, 0, 0, 597, 598, 7, 9, 0, 0, 598, 599, 7, 19, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 6, 9, 4, 0, 601, 35, 1, 0, 0, 0, 602, 603, 7, 14, 0, 0, 603, 604, 7, 10, 0, 0, 604, 605, 7, 15, 0, 0, 605, 606, 7, 10, 0, 0, 606, 607, 7, 11, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 6, 10, 4, 0, 609, 37, 1, 0, 0, 0, 610, 611, 7, 12, 0, 0, 611, 612, 7, 9, 0, 0, 612, 613, 7, 20, 0, 0, 613, 614, 1, 0, 0, 0, 614, 615, 6, 11, 4, 0, 615, 39, 1, 0, 0, 0, 616, 617, 7, 17, 0, 0, 617, 618, 7, 9, 0, 0, 618, 619, 7, 12, 0, 0, 619, 620, 7, 11, 0, 0, 620, 621, 1, 0, 0, 0, 621, 622, 6, 12, 4, 0, 622, 41, 1, 0, 0, 0, 623, 624, 7, 17, 0, 0, 624, 625, 7, 11, 0, 0, 625, 626, 7, 4, 0, 0, 626, 627, 7, 11, 0, 0, 627, 628, 7, 17, 0, 0, 628, 629, 1, 0, 0, 0, 629, 630, 6, 13, 4, 0, 630, 43, 1, 0, 0, 0, 631, 632, 7, 20, 0, 0, 632, 633, 7, 3, 0, 0, 633, 634, 7, 7, 0, 0, 634, 635, 7, 12, 0, 0, 635, 636, 7, 7, 0, 0, 636, 637, 1, 0, 0, 0, 637, 638, 6, 14, 4, 0, 638, 45, 1, 0, 0, 0, 639, 640, 4, 15, 0, 0, 640, 641, 7, 10, 0, 0, 641, 642, 7, 5, 0, 0, 642, 643, 7, 14, 0, 0, 643, 644, 7, 10, 0, 0, 644, 645, 7, 5, 0, 0, 645, 646, 7, 7, 0, 0, 646, 647, 7, 17, 0, 0, 647, 648, 7, 11, 0, 0, 648, 649, 7, 4, 0, 0, 649, 650, 7, 11, 0, 0, 650, 651, 7, 17, 0, 0, 651, 652, 1, 0, 0, 0, 652, 653, 6, 15, 4, 0, 653, 47, 1, 0, 0, 0, 654, 655, 4, 16, 1, 0, 655, 656, 7, 12, 0, 0, 656, 657, 7, 7, 0, 0, 657, 658, 7, 12, 0, 0, 658, 659, 7, 4, 0, 0, 659, 660, 7, 5, 0, 0, 660, 661, 7, 19, 0, 0, 661, 662, 1, 0, 0, 0, 662, 663, 6, 16, 4, 0, 663, 49, 1, 0, 0, 0, 664, 665, 4, 17, 2, 0, 665, 666, 7, 17, 0, 0, 666, 667, 7, 4, 0, 0, 667, 668, 7, 15, 0, 0, 668, 669, 7, 8, 0, 0, 669, 670, 7, 14, 0, 0, 670, 671, 7, 7, 0, 0, 671, 672, 1, 0, 0, 0, 672, 673, 6, 17, 4, 0, 673, 51, 1, 0, 0, 0, 674, 675, 7, 21, 0, 0, 675, 676, 7, 12, 0, 0, 676, 677, 7, 9, 0, 0, 677, 678, 7, 15, 0, 0, 678, 679, 1, 0, 0, 0, 679, 680, 6, 18, 5, 0, 680, 53, 1, 0, 0, 0, 681, 682, 4, 19, 3, 0, 682, 683, 7, 11, 0, 0, 683, 684, 7, 17, 0, 0, 684, 685, 1, 0, 0, 0, 685, 686, 6, 19, 5, 0, 686, 55, 1, 0, 0, 0, 687, 688, 4, 20, 4, 0, 688, 689, 7, 21, 0, 0, 689, 690, 7, 9, 0, 0, 690, 691, 7, 12, 0, 0, 691, 692, 7, 19, 0, 0, 692, 693, 1, 0, 0, 0, 693, 694, 6, 20, 6, 0, 694, 57, 1, 0, 0, 0, 695, 696, 7, 14, 0, 0, 696, 697, 7, 9, 0, 0, 697, 698, 7, 9, 0, 0, 698, 699, 7, 19, 0, 0, 699, 700, 7, 22, 0, 0, 700, 701, 7, 8, 0, 0, 701, 702, 1, 0, 0, 0, 702, 703, 6, 21, 7, 0, 703, 59, 1, 0, 0, 0, 704, 705, 4, 22, 5, 0, 705, 706, 7, 21, 0, 0, 706, 707, 7, 22, 0, 0, 707, 708, 7, 14, 0, 0, 708, 709, 7, 14, 0, 0, 709, 710, 1, 0, 0, 0, 710, 711, 6, 22, 7, 0, 711, 61, 1, 0, 0, 0, 712, 713, 4, 23, 6, 0, 713, 714, 7, 14, 0, 0, 714, 715, 7, 7, 0, 0, 715, 716, 7, 21, 0, 0, 716, 717, 7, 11, 0, 0, 717, 718, 1, 0, 0, 0, 718, 719, 6, 23, 7, 0, 719, 63, 1, 0, 0, 0, 720, 721, 4, 24, 7, 0, 721, 722, 7, 12, 0, 0, 722, 723, 7, 10, 0, 0, 723, 724, 7, 6, 0, 0, 724, 725, 7, 3, 0, 0, 725, 726, 7, 11, 0, 0, 726, 727, 1, 0, 0, 0, 727, 728, 6, 24, 7, 0, 728, 65, 1, 0, 0, 0, 729, 730, 4, 25, 8, 0, 730, 731, 7, 14, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 7, 9, 0, 0, 733, 734, 7, 19, 0, 0, 734, 735, 7, 22, 0, 0, 735, 736, 7, 8, 0, 0, 736, 737, 5, 95, 0, 0, 737, 738, 5, 128020, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 6, 25, 8, 0, 740, 67, 1, 0, 0, 0, 741, 742, 7, 15, 0, 0, 742, 743, 7, 18, 0, 0, 743, 744, 5, 95, 0, 0, 744, 745, 7, 7, 0, 0, 745, 746, 7, 13, 0, 0, 746, 747, 7, 8, 0, 0, 747, 748, 7, 4, 0, 0, 748, 749, 7, 5, 0, 0, 749, 750, 7, 16, 0, 0, 750, 751, 1, 0, 0, 0, 751, 752, 6, 26, 9, 0, 752, 69, 1, 0, 0, 0, 753, 754, 7, 16, 0, 0, 754, 755, 7, 12, 0, 0, 755, 756, 7, 9, 0, 0, 756, 757, 7, 8, 0, 0, 757, 758, 1, 0, 0, 0, 758, 759, 6, 27, 10, 0, 759, 71, 1, 0, 0, 0, 760, 761, 7, 19, 0, 0, 761, 762, 7, 7, 0, 0, 762, 763, 7, 7, 0, 0, 763, 764, 7, 8, 0, 0, 764, 765, 1, 0, 0, 0, 765, 766, 6, 28, 10, 0, 766, 73, 1, 0, 0, 0, 767, 768, 4, 29, 9, 0, 768, 769, 7, 10, 0, 0, 769, 770, 7, 5, 0, 0, 770, 771, 7, 17, 0, 0, 771, 772, 7, 10, 0, 0, 772, 773, 7, 17, 0, 0, 773, 774, 7, 11, 0, 0, 774, 775, 5, 95, 0, 0, 775, 776, 5, 128020, 0, 0, 776, 777, 1, 0, 0, 0, 777, 778, 6, 29, 10, 0, 778, 75, 1, 0, 0, 0, 779, 780, 4, 30, 10, 0, 780, 781, 7, 12, 0, 0, 781, 782, 7, 12, 0, 0, 782, 783, 7, 21, 0, 0, 783, 784, 1, 0, 0, 0, 784, 785, 6, 30, 4, 0, 785, 77, 1, 0, 0, 0, 786, 787, 7, 12, 0, 0, 787, 788, 7, 7, 0, 0, 788, 789, 7, 5, 0, 0, 789, 790, 7, 4, 0, 0, 790, 791, 7, 15, 0, 0, 791, 792, 7, 7, 0, 0, 792, 793, 1, 0, 0, 0, 793, 794, 6, 31, 11, 0, 794, 79, 1, 0, 0, 0, 795, 796, 7, 17, 0, 0, 796, 797, 7, 3, 0, 0, 797, 798, 7, 9, 0, 0, 798, 799, 7, 20, 0, 0, 799, 800, 1, 0, 0, 0, 800, 801, 6, 32, 12, 0, 801, 81, 1, 0, 0, 0, 802, 804, 8, 23, 0, 0, 803, 802, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 803, 1, 0, 0, 0, 805, 806, 1, 0, 0, 0, 806, 807, 1, 0, 0, 0, 807, 808, 6, 33, 4, 0, 808, 83, 1, 0, 0, 0, 809, 810, 3, 174, 79, 0, 810, 811, 1, 0, 0, 0, 811, 812, 6, 34, 13, 0, 812, 813, 6, 34, 14, 0, 813, 85, 1, 0, 0, 0, 814, 815, 3, 240, 112, 0, 815, 816, 1, 0, 0, 0, 816, 817, 6, 35, 15, 0, 817, 87, 1, 0, 0, 0, 818, 819, 3, 204, 94, 0, 819, 820, 1, 0, 0, 0, 820, 821, 6, 36, 16, 0, 821, 89, 1, 0, 0, 0, 822, 823, 3, 220, 102, 0, 823, 824, 1, 0, 0, 0, 824, 825, 6, 37, 17, 0, 825, 91, 1, 0, 0, 0, 826, 827, 3, 216, 100, 0, 827, 828, 1, 0, 0, 0, 828, 829, 6, 38, 18, 0, 829, 93, 1, 0, 0, 0, 830, 831, 3, 300, 142, 0, 831, 832, 1, 0, 0, 0, 832, 833, 6, 39, 19, 0, 833, 95, 1, 0, 0, 0, 834, 835, 3, 296, 140, 0, 835, 836, 1, 0, 0, 0, 836, 837, 6, 40, 20, 0, 837, 97, 1, 0, 0, 0, 838, 839, 3, 16, 0, 0, 839, 840, 1, 0, 0, 0, 840, 841, 6, 41, 0, 0, 841, 99, 1, 0, 0, 0, 842, 843, 3, 18, 1, 0, 843, 844, 1, 0, 0, 0, 844, 845, 6, 42, 0, 0, 845, 101, 1, 0, 0, 0, 846, 847, 3, 20, 2, 0, 847, 848, 1, 0, 0, 0, 848, 849, 6, 43, 0, 0, 849, 103, 1, 0, 0, 0, 850, 851, 3, 174, 79, 0, 851, 852, 1, 0, 0, 0, 852, 853, 6, 44, 13, 0, 853, 854, 6, 44, 14, 0, 854, 105, 1, 0, 0, 0, 855, 856, 3, 288, 136, 0, 856, 857, 1, 0, 0, 0, 857, 858, 6, 45, 21, 0, 858, 859, 6, 45, 22, 0, 859, 107, 1, 0, 0, 0, 860, 861, 3, 240, 112, 0, 861, 862, 1, 0, 0, 0, 862, 863, 6, 46, 15, 0, 863, 864, 6, 46, 23, 0, 864, 109, 1, 0, 0, 0, 865, 866, 3, 250, 117, 0, 866, 867, 1, 0, 0, 0, 867, 868, 6, 47, 24, 0, 868, 869, 6, 47, 23, 0, 869, 111, 1, 0, 0, 0, 870, 871, 8, 24, 0, 0, 871, 113, 1, 0, 0, 0, 872, 874, 3, 112, 48, 0, 873, 872, 1, 0, 0, 0, 874, 875, 1, 0, 0, 0, 875, 873, 1, 0, 0, 0, 875, 876, 1, 0, 0, 0, 876, 877, 1, 0, 0, 0, 877, 878, 3, 214, 99, 0, 878, 880, 1, 0, 0, 0, 879, 873, 1, 0, 0, 0, 879, 880, 1, 0, 0, 0, 880, 882, 1, 0, 0, 0, 881, 883, 3, 112, 48, 0, 882, 881, 1, 0, 0, 0, 883, 884, 1, 0, 0, 0, 884, 882, 1, 0, 0, 0, 884, 885, 1, 0, 0, 0, 885, 115, 1, 0, 0, 0, 886, 887, 3, 114, 49, 0, 887, 888, 1, 0, 0, 0, 888, 889, 6, 50, 25, 0, 889, 117, 1, 0, 0, 0, 890, 891, 3, 16, 0, 0, 891, 892, 1, 0, 0, 0, 892, 893, 6, 51, 0, 0, 893, 119, 1, 0, 0, 0, 894, 895, 3, 18, 1, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 52, 0, 0, 897, 121, 1, 0, 0, 0, 898, 899, 3, 20, 2, 0, 899, 900, 1, 0, 0, 0, 900, 901, 6, 53, 0, 0, 901, 123, 1, 0, 0, 0, 902, 903, 3, 174, 79, 0, 903, 904, 1, 0, 0, 0, 904, 905, 6, 54, 13, 0, 905, 906, 6, 54, 14, 0, 906, 907, 6, 54, 14, 0, 907, 125, 1, 0, 0, 0, 908, 909, 3, 208, 96, 0, 909, 910, 1, 0, 0, 0, 910, 911, 6, 55, 26, 0, 911, 127, 1, 0, 0, 0, 912, 913, 3, 216, 100, 0, 913, 914, 1, 0, 0, 0, 914, 915, 6, 56, 18, 0, 915, 129, 1, 0, 0, 0, 916, 917, 3, 220, 102, 0, 917, 918, 1, 0, 0, 0, 918, 919, 6, 57, 17, 0, 919, 131, 1, 0, 0, 0, 920, 921, 3, 250, 117, 0, 921, 922, 1, 0, 0, 0, 922, 923, 6, 58, 24, 0, 923, 133, 1, 0, 0, 0, 924, 925, 3, 448, 216, 0, 925, 926, 1, 0, 0, 0, 926, 927, 6, 59, 27, 0, 927, 135, 1, 0, 0, 0, 928, 929, 3, 300, 142, 0, 929, 930, 1, 0, 0, 0, 930, 931, 6, 60, 19, 0, 931, 137, 1, 0, 0, 0, 932, 933, 3, 244, 114, 0, 933, 934, 1, 0, 0, 0, 934, 935, 6, 61, 28, 0, 935, 139, 1, 0, 0, 0, 936, 937, 3, 284, 134, 0, 937, 938, 1, 0, 0, 0, 938, 939, 6, 62, 29, 0, 939, 141, 1, 0, 0, 0, 940, 941, 3, 280, 132, 0, 941, 942, 1, 0, 0, 0, 942, 943, 6, 63, 30, 0, 943, 143, 1, 0, 0, 0, 944, 945, 3, 286, 135, 0, 945, 946, 1, 0, 0, 0, 946, 947, 6, 64, 31, 0, 947, 145, 1, 0, 0, 0, 948, 949, 3, 16, 0, 0, 949, 950, 1, 0, 0, 0, 950, 951, 6, 65, 0, 0, 951, 147, 1, 0, 0, 0, 952, 953, 3, 18, 1, 0, 953, 954, 1, 0, 0, 0, 954, 955, 6, 66, 0, 0, 955, 149, 1, 0, 0, 0, 956, 957, 3, 20, 2, 0, 957, 958, 1, 0, 0, 0, 958, 959, 6, 67, 0, 0, 959, 151, 1, 0, 0, 0, 960, 961, 3, 290, 137, 0, 961, 962, 1, 0, 0, 0, 962, 963, 6, 68, 32, 0, 963, 964, 6, 68, 14, 0, 964, 153, 1, 0, 0, 0, 965, 966, 3, 214, 99, 0, 966, 967, 1, 0, 0, 0, 967, 968, 6, 69, 33, 0, 968, 155, 1, 0, 0, 0, 969, 975, 3, 186, 85, 0, 970, 975, 3, 176, 80, 0, 971, 975, 3, 220, 102, 0, 972, 975, 3, 178, 81, 0, 973, 975, 3, 192, 88, 0, 974, 969, 1, 0, 0, 0, 974, 970, 1, 0, 0, 0, 974, 971, 1, 0, 0, 0, 974, 972, 1, 0, 0, 0, 974, 973, 1, 0, 0, 0, 975, 976, 1, 0, 0, 0, 976, 974, 1, 0, 0, 0, 976, 977, 1, 0, 0, 0, 977, 157, 1, 0, 0, 0, 978, 979, 3, 16, 0, 0, 979, 980, 1, 0, 0, 0, 980, 981, 6, 71, 0, 0, 981, 159, 1, 0, 0, 0, 982, 983, 3, 18, 1, 0, 983, 984, 1, 0, 0, 0, 984, 985, 6, 72, 0, 0, 985, 161, 1, 0, 0, 0, 986, 987, 3, 20, 2, 0, 987, 988, 1, 0, 0, 0, 988, 989, 6, 73, 0, 0, 989, 163, 1, 0, 0, 0, 990, 991, 3, 288, 136, 0, 991, 992, 1, 0, 0, 0, 992, 993, 6, 74, 21, 0, 993, 994, 6, 74, 34, 0, 994, 165, 1, 0, 0, 0, 995, 996, 3, 174, 79, 0, 996, 997, 1, 0, 0, 0, 997, 998, 6, 75, 13, 0, 998, 999, 6, 75, 14, 0, 999, 167, 1, 0, 0, 0, 1000, 1001, 3, 20, 2, 0, 1001, 1002, 1, 0, 0, 0, 1002, 1003, 6, 76, 0, 0, 1003, 169, 1, 0, 0, 0, 1004, 1005, 3, 16, 0, 0, 1005, 1006, 1, 0, 0, 0, 1006, 1007, 6, 77, 0, 0, 1007, 171, 1, 0, 0, 0, 1008, 1009, 3, 18, 1, 0, 1009, 1010, 1, 0, 0, 0, 1010, 1011, 6, 78, 0, 0, 1011, 173, 1, 0, 0, 0, 1012, 1013, 5, 124, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 6, 79, 14, 0, 1015, 175, 1, 0, 0, 0, 1016, 1017, 7, 25, 0, 0, 1017, 177, 1, 0, 0, 0, 1018, 1019, 7, 26, 0, 0, 1019, 179, 1, 0, 0, 0, 1020, 1021, 5, 92, 0, 0, 1021, 1022, 7, 27, 0, 0, 1022, 181, 1, 0, 0, 0, 1023, 1024, 8, 28, 0, 0, 1024, 183, 1, 0, 0, 0, 1025, 1027, 7, 7, 0, 0, 1026, 1028, 7, 29, 0, 0, 1027, 1026, 1, 0, 0, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1030, 1, 0, 0, 0, 1029, 1031, 3, 176, 80, 0, 1030, 1029, 1, 0, 0, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1030, 1, 0, 0, 0, 1032, 1033, 1, 0, 0, 0, 1033, 185, 1, 0, 0, 0, 1034, 1035, 5, 64, 0, 0, 1035, 187, 1, 0, 0, 0, 1036, 1037, 5, 96, 0, 0, 1037, 189, 1, 0, 0, 0, 1038, 1042, 8, 30, 0, 0, 1039, 1040, 5, 96, 0, 0, 1040, 1042, 5, 96, 0, 0, 1041, 1038, 1, 0, 0, 0, 1041, 1039, 1, 0, 0, 0, 1042, 191, 1, 0, 0, 0, 1043, 1044, 5, 95, 0, 0, 1044, 193, 1, 0, 0, 0, 1045, 1049, 3, 178, 81, 0, 1046, 1049, 3, 176, 80, 0, 1047, 1049, 3, 192, 88, 0, 1048, 1045, 1, 0, 0, 0, 1048, 1046, 1, 0, 0, 0, 1048, 1047, 1, 0, 0, 0, 1049, 195, 1, 0, 0, 0, 1050, 1055, 5, 34, 0, 0, 1051, 1054, 3, 180, 82, 0, 1052, 1054, 3, 182, 83, 0, 1053, 1051, 1, 0, 0, 0, 1053, 1052, 1, 0, 0, 0, 1054, 1057, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1056, 1, 0, 0, 0, 1056, 1058, 1, 0, 0, 0, 1057, 1055, 1, 0, 0, 0, 1058, 1080, 5, 34, 0, 0, 1059, 1060, 5, 34, 0, 0, 1060, 1061, 5, 34, 0, 0, 1061, 1062, 5, 34, 0, 0, 1062, 1066, 1, 0, 0, 0, 1063, 1065, 8, 0, 0, 0, 1064, 1063, 1, 0, 0, 0, 1065, 1068, 1, 0, 0, 0, 1066, 1067, 1, 0, 0, 0, 1066, 1064, 1, 0, 0, 0, 1067, 1069, 1, 0, 0, 0, 1068, 1066, 1, 0, 0, 0, 1069, 1070, 5, 34, 0, 0, 1070, 1071, 5, 34, 0, 0, 1071, 1072, 5, 34, 0, 0, 1072, 1074, 1, 0, 0, 0, 1073, 1075, 5, 34, 0, 0, 1074, 1073, 1, 0, 0, 0, 1074, 1075, 1, 0, 0, 0, 1075, 1077, 1, 0, 0, 0, 1076, 1078, 5, 34, 0, 0, 1077, 1076, 1, 0, 0, 0, 1077, 1078, 1, 0, 0, 0, 1078, 1080, 1, 0, 0, 0, 1079, 1050, 1, 0, 0, 0, 1079, 1059, 1, 0, 0, 0, 1080, 197, 1, 0, 0, 0, 1081, 1083, 3, 176, 80, 0, 1082, 1081, 1, 0, 0, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1082, 1, 0, 0, 0, 1084, 1085, 1, 0, 0, 0, 1085, 199, 1, 0, 0, 0, 1086, 1088, 3, 176, 80, 0, 1087, 1086, 1, 0, 0, 0, 1088, 1089, 1, 0, 0, 0, 1089, 1087, 1, 0, 0, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 1, 0, 0, 0, 1091, 1095, 3, 220, 102, 0, 1092, 1094, 3, 176, 80, 0, 1093, 1092, 1, 0, 0, 0, 1094, 1097, 1, 0, 0, 0, 1095, 1093, 1, 0, 0, 0, 1095, 1096, 1, 0, 0, 0, 1096, 1129, 1, 0, 0, 0, 1097, 1095, 1, 0, 0, 0, 1098, 1100, 3, 220, 102, 0, 1099, 1101, 3, 176, 80, 0, 1100, 1099, 1, 0, 0, 0, 1101, 1102, 1, 0, 0, 0, 1102, 1100, 1, 0, 0, 0, 1102, 1103, 1, 0, 0, 0, 1103, 1129, 1, 0, 0, 0, 1104, 1106, 3, 176, 80, 0, 1105, 1104, 1, 0, 0, 0, 1106, 1107, 1, 0, 0, 0, 1107, 1105, 1, 0, 0, 0, 1107, 1108, 1, 0, 0, 0, 1108, 1116, 1, 0, 0, 0, 1109, 1113, 3, 220, 102, 0, 1110, 1112, 3, 176, 80, 0, 1111, 1110, 1, 0, 0, 0, 1112, 1115, 1, 0, 0, 0, 1113, 1111, 1, 0, 0, 0, 1113, 1114, 1, 0, 0, 0, 1114, 1117, 1, 0, 0, 0, 1115, 1113, 1, 0, 0, 0, 1116, 1109, 1, 0, 0, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1118, 1, 0, 0, 0, 1118, 1119, 3, 184, 84, 0, 1119, 1129, 1, 0, 0, 0, 1120, 1122, 3, 220, 102, 0, 1121, 1123, 3, 176, 80, 0, 1122, 1121, 1, 0, 0, 0, 1123, 1124, 1, 0, 0, 0, 1124, 1122, 1, 0, 0, 0, 1124, 1125, 1, 0, 0, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 3, 184, 84, 0, 1127, 1129, 1, 0, 0, 0, 1128, 1087, 1, 0, 0, 0, 1128, 1098, 1, 0, 0, 0, 1128, 1105, 1, 0, 0, 0, 1128, 1120, 1, 0, 0, 0, 1129, 201, 1, 0, 0, 0, 1130, 1131, 7, 4, 0, 0, 1131, 1132, 7, 5, 0, 0, 1132, 1133, 7, 16, 0, 0, 1133, 203, 1, 0, 0, 0, 1134, 1135, 7, 4, 0, 0, 1135, 1136, 7, 17, 0, 0, 1136, 205, 1, 0, 0, 0, 1137, 1138, 7, 4, 0, 0, 1138, 1139, 7, 17, 0, 0, 1139, 1140, 7, 2, 0, 0, 1140, 207, 1, 0, 0, 0, 1141, 1142, 5, 61, 0, 0, 1142, 209, 1, 0, 0, 0, 1143, 1144, 7, 31, 0, 0, 1144, 1145, 7, 32, 0, 0, 1145, 211, 1, 0, 0, 0, 1146, 1147, 5, 58, 0, 0, 1147, 1148, 5, 58, 0, 0, 1148, 213, 1, 0, 0, 0, 1149, 1150, 5, 58, 0, 0, 1150, 215, 1, 0, 0, 0, 1151, 1152, 5, 44, 0, 0, 1152, 217, 1, 0, 0, 0, 1153, 1154, 7, 16, 0, 0, 1154, 1155, 7, 7, 0, 0, 1155, 1156, 7, 17, 0, 0, 1156, 1157, 7, 2, 0, 0, 1157, 219, 1, 0, 0, 0, 1158, 1159, 5, 46, 0, 0, 1159, 221, 1, 0, 0, 0, 1160, 1161, 7, 21, 0, 0, 1161, 1162, 7, 4, 0, 0, 1162, 1163, 7, 14, 0, 0, 1163, 1164, 7, 17, 0, 0, 1164, 1165, 7, 7, 0, 0, 1165, 223, 1, 0, 0, 0, 1166, 1167, 7, 21, 0, 0, 1167, 1168, 7, 10, 0, 0, 1168, 1169, 7, 12, 0, 0, 1169, 1170, 7, 17, 0, 0, 1170, 1171, 7, 11, 0, 0, 1171, 225, 1, 0, 0, 0, 1172, 1173, 7, 10, 0, 0, 1173, 1174, 7, 5, 0, 0, 1174, 227, 1, 0, 0, 0, 1175, 1176, 7, 10, 0, 0, 1176, 1177, 7, 17, 0, 0, 1177, 229, 1, 0, 0, 0, 1178, 1179, 7, 14, 0, 0, 1179, 1180, 7, 4, 0, 0, 1180, 1181, 7, 17, 0, 0, 1181, 1182, 7, 11, 0, 0, 1182, 231, 1, 0, 0, 0, 1183, 1184, 7, 14, 0, 0, 1184, 1185, 7, 10, 0, 0, 1185, 1186, 7, 19, 0, 0, 1186, 1187, 7, 7, 0, 0, 1187, 233, 1, 0, 0, 0, 1188, 1189, 7, 5, 0, 0, 1189, 1190, 7, 9, 0, 0, 1190, 1191, 7, 11, 0, 0, 1191, 235, 1, 0, 0, 0, 1192, 1193, 7, 5, 0, 0, 1193, 1194, 7, 22, 0, 0, 1194, 1195, 7, 14, 0, 0, 1195, 1196, 7, 14, 0, 0, 1196, 237, 1, 0, 0, 0, 1197, 1198, 7, 5, 0, 0, 1198, 1199, 7, 22, 0, 0, 1199, 1200, 7, 14, 0, 0, 1200, 1201, 7, 14, 0, 0, 1201, 1202, 7, 17, 0, 0, 1202, 239, 1, 0, 0, 0, 1203, 1204, 7, 9, 0, 0, 1204, 1205, 7, 5, 0, 0, 1205, 241, 1, 0, 0, 0, 1206, 1207, 7, 9, 0, 0, 1207, 1208, 7, 12, 0, 0, 1208, 243, 1, 0, 0, 0, 1209, 1210, 5, 63, 0, 0, 1210, 245, 1, 0, 0, 0, 1211, 1212, 7, 12, 0, 0, 1212, 1213, 7, 14, 0, 0, 1213, 1214, 7, 10, 0, 0, 1214, 1215, 7, 19, 0, 0, 1215, 1216, 7, 7, 0, 0, 1216, 247, 1, 0, 0, 0, 1217, 1218, 7, 11, 0, 0, 1218, 1219, 7, 12, 0, 0, 1219, 1220, 7, 22, 0, 0, 1220, 1221, 7, 7, 0, 0, 1221, 249, 1, 0, 0, 0, 1222, 1223, 7, 20, 0, 0, 1223, 1224, 7, 10, 0, 0, 1224, 1225, 7, 11, 0, 0, 1225, 1226, 7, 3, 0, 0, 1226, 251, 1, 0, 0, 0, 1227, 1228, 5, 61, 0, 0, 1228, 1229, 5, 61, 0, 0, 1229, 253, 1, 0, 0, 0, 1230, 1231, 5, 61, 0, 0, 1231, 1232, 5, 126, 0, 0, 1232, 255, 1, 0, 0, 0, 1233, 1234, 5, 33, 0, 0, 1234, 1235, 5, 61, 0, 0, 1235, 257, 1, 0, 0, 0, 1236, 1237, 5, 60, 0, 0, 1237, 259, 1, 0, 0, 0, 1238, 1239, 5, 60, 0, 0, 1239, 1240, 5, 61, 0, 0, 1240, 261, 1, 0, 0, 0, 1241, 1242, 5, 62, 0, 0, 1242, 263, 1, 0, 0, 0, 1243, 1244, 5, 62, 0, 0, 1244, 1245, 5, 61, 0, 0, 1245, 265, 1, 0, 0, 0, 1246, 1247, 5, 43, 0, 0, 1247, 267, 1, 0, 0, 0, 1248, 1249, 5, 45, 0, 0, 1249, 269, 1, 0, 0, 0, 1250, 1251, 5, 42, 0, 0, 1251, 271, 1, 0, 0, 0, 1252, 1253, 5, 47, 0, 0, 1253, 273, 1, 0, 0, 0, 1254, 1255, 5, 37, 0, 0, 1255, 275, 1, 0, 0, 0, 1256, 1257, 5, 123, 0, 0, 1257, 277, 1, 0, 0, 0, 1258, 1259, 5, 125, 0, 0, 1259, 279, 1, 0, 0, 0, 1260, 1261, 5, 63, 0, 0, 1261, 1262, 5, 63, 0, 0, 1262, 281, 1, 0, 0, 0, 1263, 1264, 3, 44, 14, 0, 1264, 1265, 1, 0, 0, 0, 1265, 1266, 6, 133, 35, 0, 1266, 283, 1, 0, 0, 0, 1267, 1270, 3, 244, 114, 0, 1268, 1271, 3, 178, 81, 0, 1269, 1271, 3, 192, 88, 0, 1270, 1268, 1, 0, 0, 0, 1270, 1269, 1, 0, 0, 0, 1271, 1275, 1, 0, 0, 0, 1272, 1274, 3, 194, 89, 0, 1273, 1272, 1, 0, 0, 0, 1274, 1277, 1, 0, 0, 0, 1275, 1273, 1, 0, 0, 0, 1275, 1276, 1, 0, 0, 0, 1276, 1285, 1, 0, 0, 0, 1277, 1275, 1, 0, 0, 0, 1278, 1280, 3, 244, 114, 0, 1279, 1281, 3, 176, 80, 0, 1280, 1279, 1, 0, 0, 0, 1281, 1282, 1, 0, 0, 0, 1282, 1280, 1, 0, 0, 0, 1282, 1283, 1, 0, 0, 0, 1283, 1285, 1, 0, 0, 0, 1284, 1267, 1, 0, 0, 0, 1284, 1278, 1, 0, 0, 0, 1285, 285, 1, 0, 0, 0, 1286, 1289, 3, 280, 132, 0, 1287, 1290, 3, 178, 81, 0, 1288, 1290, 3, 192, 88, 0, 1289, 1287, 1, 0, 0, 0, 1289, 1288, 1, 0, 0, 0, 1290, 1294, 1, 0, 0, 0, 1291, 1293, 3, 194, 89, 0, 1292, 1291, 1, 0, 0, 0, 1293, 1296, 1, 0, 0, 0, 1294, 1292, 1, 0, 0, 0, 1294, 1295, 1, 0, 0, 0, 1295, 1304, 1, 0, 0, 0, 1296, 1294, 1, 0, 0, 0, 1297, 1299, 3, 280, 132, 0, 1298, 1300, 3, 176, 80, 0, 1299, 1298, 1, 0, 0, 0, 1300, 1301, 1, 0, 0, 0, 1301, 1299, 1, 0, 0, 0, 1301, 1302, 1, 0, 0, 0, 1302, 1304, 1, 0, 0, 0, 1303, 1286, 1, 0, 0, 0, 1303, 1297, 1, 0, 0, 0, 1304, 287, 1, 0, 0, 0, 1305, 1306, 5, 91, 0, 0, 1306, 1307, 1, 0, 0, 0, 1307, 1308, 6, 136, 4, 0, 1308, 1309, 6, 136, 4, 0, 1309, 289, 1, 0, 0, 0, 1310, 1311, 5, 93, 0, 0, 1311, 1312, 1, 0, 0, 0, 1312, 1313, 6, 137, 14, 0, 1313, 1314, 6, 137, 14, 0, 1314, 291, 1, 0, 0, 0, 1315, 1316, 5, 40, 0, 0, 1316, 1317, 1, 0, 0, 0, 1317, 1318, 6, 138, 4, 0, 1318, 1319, 6, 138, 4, 0, 1319, 293, 1, 0, 0, 0, 1320, 1321, 5, 41, 0, 0, 1321, 1322, 1, 0, 0, 0, 1322, 1323, 6, 139, 14, 0, 1323, 1324, 6, 139, 14, 0, 1324, 295, 1, 0, 0, 0, 1325, 1329, 3, 178, 81, 0, 1326, 1328, 3, 194, 89, 0, 1327, 1326, 1, 0, 0, 0, 1328, 1331, 1, 0, 0, 0, 1329, 1327, 1, 0, 0, 0, 1329, 1330, 1, 0, 0, 0, 1330, 1342, 1, 0, 0, 0, 1331, 1329, 1, 0, 0, 0, 1332, 1335, 3, 192, 88, 0, 1333, 1335, 3, 186, 85, 0, 1334, 1332, 1, 0, 0, 0, 1334, 1333, 1, 0, 0, 0, 1335, 1337, 1, 0, 0, 0, 1336, 1338, 3, 194, 89, 0, 1337, 1336, 1, 0, 0, 0, 1338, 1339, 1, 0, 0, 0, 1339, 1337, 1, 0, 0, 0, 1339, 1340, 1, 0, 0, 0, 1340, 1342, 1, 0, 0, 0, 1341, 1325, 1, 0, 0, 0, 1341, 1334, 1, 0, 0, 0, 1342, 297, 1, 0, 0, 0, 1343, 1345, 3, 188, 86, 0, 1344, 1346, 3, 190, 87, 0, 1345, 1344, 1, 0, 0, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1345, 1, 0, 0, 0, 1347, 1348, 1, 0, 0, 0, 1348, 1349, 1, 0, 0, 0, 1349, 1350, 3, 188, 86, 0, 1350, 299, 1, 0, 0, 0, 1351, 1352, 3, 298, 141, 0, 1352, 301, 1, 0, 0, 0, 1353, 1354, 3, 16, 0, 0, 1354, 1355, 1, 0, 0, 0, 1355, 1356, 6, 143, 0, 0, 1356, 303, 1, 0, 0, 0, 1357, 1358, 3, 18, 1, 0, 1358, 1359, 1, 0, 0, 0, 1359, 1360, 6, 144, 0, 0, 1360, 305, 1, 0, 0, 0, 1361, 1362, 3, 20, 2, 0, 1362, 1363, 1, 0, 0, 0, 1363, 1364, 6, 145, 0, 0, 1364, 307, 1, 0, 0, 0, 1365, 1366, 3, 174, 79, 0, 1366, 1367, 1, 0, 0, 0, 1367, 1368, 6, 146, 13, 0, 1368, 1369, 6, 146, 14, 0, 1369, 309, 1, 0, 0, 0, 1370, 1371, 3, 288, 136, 0, 1371, 1372, 1, 0, 0, 0, 1372, 1373, 6, 147, 21, 0, 1373, 311, 1, 0, 0, 0, 1374, 1375, 3, 290, 137, 0, 1375, 1376, 1, 0, 0, 0, 1376, 1377, 6, 148, 32, 0, 1377, 313, 1, 0, 0, 0, 1378, 1379, 3, 214, 99, 0, 1379, 1380, 1, 0, 0, 0, 1380, 1381, 6, 149, 33, 0, 1381, 315, 1, 0, 0, 0, 1382, 1383, 3, 212, 98, 0, 1383, 1384, 1, 0, 0, 0, 1384, 1385, 6, 150, 36, 0, 1385, 317, 1, 0, 0, 0, 1386, 1387, 3, 216, 100, 0, 1387, 1388, 1, 0, 0, 0, 1388, 1389, 6, 151, 18, 0, 1389, 319, 1, 0, 0, 0, 1390, 1391, 3, 208, 96, 0, 1391, 1392, 1, 0, 0, 0, 1392, 1393, 6, 152, 26, 0, 1393, 321, 1, 0, 0, 0, 1394, 1395, 7, 15, 0, 0, 1395, 1396, 7, 7, 0, 0, 1396, 1397, 7, 11, 0, 0, 1397, 1398, 7, 4, 0, 0, 1398, 1399, 7, 16, 0, 0, 1399, 1400, 7, 4, 0, 0, 1400, 1401, 7, 11, 0, 0, 1401, 1402, 7, 4, 0, 0, 1402, 323, 1, 0, 0, 0, 1403, 1407, 8, 33, 0, 0, 1404, 1405, 5, 47, 0, 0, 1405, 1407, 8, 34, 0, 0, 1406, 1403, 1, 0, 0, 0, 1406, 1404, 1, 0, 0, 0, 1407, 325, 1, 0, 0, 0, 1408, 1410, 3, 324, 154, 0, 1409, 1408, 1, 0, 0, 0, 1410, 1411, 1, 0, 0, 0, 1411, 1409, 1, 0, 0, 0, 1411, 1412, 1, 0, 0, 0, 1412, 327, 1, 0, 0, 0, 1413, 1414, 3, 326, 155, 0, 1414, 1415, 1, 0, 0, 0, 1415, 1416, 6, 156, 37, 0, 1416, 329, 1, 0, 0, 0, 1417, 1418, 3, 196, 90, 0, 1418, 1419, 1, 0, 0, 0, 1419, 1420, 6, 157, 38, 0, 1420, 331, 1, 0, 0, 0, 1421, 1422, 3, 16, 0, 0, 1422, 1423, 1, 0, 0, 0, 1423, 1424, 6, 158, 0, 0, 1424, 333, 1, 0, 0, 0, 1425, 1426, 3, 18, 1, 0, 1426, 1427, 1, 0, 0, 0, 1427, 1428, 6, 159, 0, 0, 1428, 335, 1, 0, 0, 0, 1429, 1430, 3, 20, 2, 0, 1430, 1431, 1, 0, 0, 0, 1431, 1432, 6, 160, 0, 0, 1432, 337, 1, 0, 0, 0, 1433, 1434, 3, 292, 138, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 161, 39, 0, 1436, 1437, 6, 161, 34, 0, 1437, 339, 1, 0, 0, 0, 1438, 1439, 3, 174, 79, 0, 1439, 1440, 1, 0, 0, 0, 1440, 1441, 6, 162, 13, 0, 1441, 1442, 6, 162, 14, 0, 1442, 341, 1, 0, 0, 0, 1443, 1444, 3, 20, 2, 0, 1444, 1445, 1, 0, 0, 0, 1445, 1446, 6, 163, 0, 0, 1446, 343, 1, 0, 0, 0, 1447, 1448, 3, 16, 0, 0, 1448, 1449, 1, 0, 0, 0, 1449, 1450, 6, 164, 0, 0, 1450, 345, 1, 0, 0, 0, 1451, 1452, 3, 18, 1, 0, 1452, 1453, 1, 0, 0, 0, 1453, 1454, 6, 165, 0, 0, 1454, 347, 1, 0, 0, 0, 1455, 1456, 3, 174, 79, 0, 1456, 1457, 1, 0, 0, 0, 1457, 1458, 6, 166, 13, 0, 1458, 1459, 6, 166, 14, 0, 1459, 349, 1, 0, 0, 0, 1460, 1461, 7, 35, 0, 0, 1461, 1462, 7, 9, 0, 0, 1462, 1463, 7, 10, 0, 0, 1463, 1464, 7, 5, 0, 0, 1464, 351, 1, 0, 0, 0, 1465, 1466, 3, 204, 94, 0, 1466, 1467, 1, 0, 0, 0, 1467, 1468, 6, 168, 16, 0, 1468, 353, 1, 0, 0, 0, 1469, 1470, 3, 240, 112, 0, 1470, 1471, 1, 0, 0, 0, 1471, 1472, 6, 169, 15, 0, 1472, 1473, 6, 169, 14, 0, 1473, 1474, 6, 169, 4, 0, 1474, 355, 1, 0, 0, 0, 1475, 1476, 7, 22, 0, 0, 1476, 1477, 7, 17, 0, 0, 1477, 1478, 7, 10, 0, 0, 1478, 1479, 7, 5, 0, 0, 1479, 1480, 7, 6, 0, 0, 1480, 1481, 1, 0, 0, 0, 1481, 1482, 6, 170, 14, 0, 1482, 1483, 6, 170, 4, 0, 1483, 357, 1, 0, 0, 0, 1484, 1485, 3, 326, 155, 0, 1485, 1486, 1, 0, 0, 0, 1486, 1487, 6, 171, 37, 0, 1487, 359, 1, 0, 0, 0, 1488, 1489, 3, 196, 90, 0, 1489, 1490, 1, 0, 0, 0, 1490, 1491, 6, 172, 38, 0, 1491, 361, 1, 0, 0, 0, 1492, 1493, 3, 214, 99, 0, 1493, 1494, 1, 0, 0, 0, 1494, 1495, 6, 173, 33, 0, 1495, 363, 1, 0, 0, 0, 1496, 1497, 3, 296, 140, 0, 1497, 1498, 1, 0, 0, 0, 1498, 1499, 6, 174, 20, 0, 1499, 365, 1, 0, 0, 0, 1500, 1501, 3, 300, 142, 0, 1501, 1502, 1, 0, 0, 0, 1502, 1503, 6, 175, 19, 0, 1503, 367, 1, 0, 0, 0, 1504, 1505, 3, 16, 0, 0, 1505, 1506, 1, 0, 0, 0, 1506, 1507, 6, 176, 0, 0, 1507, 369, 1, 0, 0, 0, 1508, 1509, 3, 18, 1, 0, 1509, 1510, 1, 0, 0, 0, 1510, 1511, 6, 177, 0, 0, 1511, 371, 1, 0, 0, 0, 1512, 1513, 3, 20, 2, 0, 1513, 1514, 1, 0, 0, 0, 1514, 1515, 6, 178, 0, 0, 1515, 373, 1, 0, 0, 0, 1516, 1517, 3, 174, 79, 0, 1517, 1518, 1, 0, 0, 0, 1518, 1519, 6, 179, 13, 0, 1519, 1520, 6, 179, 14, 0, 1520, 375, 1, 0, 0, 0, 1521, 1522, 3, 214, 99, 0, 1522, 1523, 1, 0, 0, 0, 1523, 1524, 6, 180, 33, 0, 1524, 377, 1, 0, 0, 0, 1525, 1526, 3, 216, 100, 0, 1526, 1527, 1, 0, 0, 0, 1527, 1528, 6, 181, 18, 0, 1528, 379, 1, 0, 0, 0, 1529, 1530, 3, 220, 102, 0, 1530, 1531, 1, 0, 0, 0, 1531, 1532, 6, 182, 17, 0, 1532, 381, 1, 0, 0, 0, 1533, 1534, 3, 240, 112, 0, 1534, 1535, 1, 0, 0, 0, 1535, 1536, 6, 183, 15, 0, 1536, 1537, 6, 183, 40, 0, 1537, 383, 1, 0, 0, 0, 1538, 1539, 3, 326, 155, 0, 1539, 1540, 1, 0, 0, 0, 1540, 1541, 6, 184, 37, 0, 1541, 385, 1, 0, 0, 0, 1542, 1543, 3, 196, 90, 0, 1543, 1544, 1, 0, 0, 0, 1544, 1545, 6, 185, 38, 0, 1545, 387, 1, 0, 0, 0, 1546, 1547, 3, 16, 0, 0, 1547, 1548, 1, 0, 0, 0, 1548, 1549, 6, 186, 0, 0, 1549, 389, 1, 0, 0, 0, 1550, 1551, 3, 18, 1, 0, 1551, 1552, 1, 0, 0, 0, 1552, 1553, 6, 187, 0, 0, 1553, 391, 1, 0, 0, 0, 1554, 1555, 3, 20, 2, 0, 1555, 1556, 1, 0, 0, 0, 1556, 1557, 6, 188, 0, 0, 1557, 393, 1, 0, 0, 0, 1558, 1559, 3, 174, 79, 0, 1559, 1560, 1, 0, 0, 0, 1560, 1561, 6, 189, 13, 0, 1561, 1562, 6, 189, 14, 0, 1562, 1563, 6, 189, 14, 0, 1563, 395, 1, 0, 0, 0, 1564, 1565, 3, 216, 100, 0, 1565, 1566, 1, 0, 0, 0, 1566, 1567, 6, 190, 18, 0, 1567, 397, 1, 0, 0, 0, 1568, 1569, 3, 220, 102, 0, 1569, 1570, 1, 0, 0, 0, 1570, 1571, 6, 191, 17, 0, 1571, 399, 1, 0, 0, 0, 1572, 1573, 3, 448, 216, 0, 1573, 1574, 1, 0, 0, 0, 1574, 1575, 6, 192, 27, 0, 1575, 401, 1, 0, 0, 0, 1576, 1577, 3, 16, 0, 0, 1577, 1578, 1, 0, 0, 0, 1578, 1579, 6, 193, 0, 0, 1579, 403, 1, 0, 0, 0, 1580, 1581, 3, 18, 1, 0, 1581, 1582, 1, 0, 0, 0, 1582, 1583, 6, 194, 0, 0, 1583, 405, 1, 0, 0, 0, 1584, 1585, 3, 20, 2, 0, 1585, 1586, 1, 0, 0, 0, 1586, 1587, 6, 195, 0, 0, 1587, 407, 1, 0, 0, 0, 1588, 1589, 3, 174, 79, 0, 1589, 1590, 1, 0, 0, 0, 1590, 1591, 6, 196, 13, 0, 1591, 1592, 6, 196, 14, 0, 1592, 409, 1, 0, 0, 0, 1593, 1594, 3, 220, 102, 0, 1594, 1595, 1, 0, 0, 0, 1595, 1596, 6, 197, 17, 0, 1596, 411, 1, 0, 0, 0, 1597, 1598, 3, 244, 114, 0, 1598, 1599, 1, 0, 0, 0, 1599, 1600, 6, 198, 28, 0, 1600, 413, 1, 0, 0, 0, 1601, 1602, 3, 284, 134, 0, 1602, 1603, 1, 0, 0, 0, 1603, 1604, 6, 199, 29, 0, 1604, 415, 1, 0, 0, 0, 1605, 1606, 3, 280, 132, 0, 1606, 1607, 1, 0, 0, 0, 1607, 1608, 6, 200, 30, 0, 1608, 417, 1, 0, 0, 0, 1609, 1610, 3, 286, 135, 0, 1610, 1611, 1, 0, 0, 0, 1611, 1612, 6, 201, 31, 0, 1612, 419, 1, 0, 0, 0, 1613, 1614, 3, 300, 142, 0, 1614, 1615, 1, 0, 0, 0, 1615, 1616, 6, 202, 19, 0, 1616, 421, 1, 0, 0, 0, 1617, 1618, 3, 296, 140, 0, 1618, 1619, 1, 0, 0, 0, 1619, 1620, 6, 203, 20, 0, 1620, 423, 1, 0, 0, 0, 1621, 1622, 3, 16, 0, 0, 1622, 1623, 1, 0, 0, 0, 1623, 1624, 6, 204, 0, 0, 1624, 425, 1, 0, 0, 0, 1625, 1626, 3, 18, 1, 0, 1626, 1627, 1, 0, 0, 0, 1627, 1628, 6, 205, 0, 0, 1628, 427, 1, 0, 0, 0, 1629, 1630, 3, 20, 2, 0, 1630, 1631, 1, 0, 0, 0, 1631, 1632, 6, 206, 0, 0, 1632, 429, 1, 0, 0, 0, 1633, 1634, 3, 174, 79, 0, 1634, 1635, 1, 0, 0, 0, 1635, 1636, 6, 207, 13, 0, 1636, 1637, 6, 207, 14, 0, 1637, 431, 1, 0, 0, 0, 1638, 1639, 3, 220, 102, 0, 1639, 1640, 1, 0, 0, 0, 1640, 1641, 6, 208, 17, 0, 1641, 433, 1, 0, 0, 0, 1642, 1643, 3, 216, 100, 0, 1643, 1644, 1, 0, 0, 0, 1644, 1645, 6, 209, 18, 0, 1645, 435, 1, 0, 0, 0, 1646, 1647, 3, 244, 114, 0, 1647, 1648, 1, 0, 0, 0, 1648, 1649, 6, 210, 28, 0, 1649, 437, 1, 0, 0, 0, 1650, 1651, 3, 284, 134, 0, 1651, 1652, 1, 0, 0, 0, 1652, 1653, 6, 211, 29, 0, 1653, 439, 1, 0, 0, 0, 1654, 1655, 3, 280, 132, 0, 1655, 1656, 1, 0, 0, 0, 1656, 1657, 6, 212, 30, 0, 1657, 441, 1, 0, 0, 0, 1658, 1659, 3, 286, 135, 0, 1659, 1660, 1, 0, 0, 0, 1660, 1661, 6, 213, 31, 0, 1661, 443, 1, 0, 0, 0, 1662, 1667, 3, 178, 81, 0, 1663, 1667, 3, 176, 80, 0, 1664, 1667, 3, 192, 88, 0, 1665, 1667, 3, 270, 127, 0, 1666, 1662, 1, 0, 0, 0, 1666, 1663, 1, 0, 0, 0, 1666, 1664, 1, 0, 0, 0, 1666, 1665, 1, 0, 0, 0, 1667, 445, 1, 0, 0, 0, 1668, 1671, 3, 178, 81, 0, 1669, 1671, 3, 270, 127, 0, 1670, 1668, 1, 0, 0, 0, 1670, 1669, 1, 0, 0, 0, 1671, 1675, 1, 0, 0, 0, 1672, 1674, 3, 444, 214, 0, 1673, 1672, 1, 0, 0, 0, 1674, 1677, 1, 0, 0, 0, 1675, 1673, 1, 0, 0, 0, 1675, 1676, 1, 0, 0, 0, 1676, 1688, 1, 0, 0, 0, 1677, 1675, 1, 0, 0, 0, 1678, 1681, 3, 192, 88, 0, 1679, 1681, 3, 186, 85, 0, 1680, 1678, 1, 0, 0, 0, 1680, 1679, 1, 0, 0, 0, 1681, 1683, 1, 0, 0, 0, 1682, 1684, 3, 444, 214, 0, 1683, 1682, 1, 0, 0, 0, 1684, 1685, 1, 0, 0, 0, 1685, 1683, 1, 0, 0, 0, 1685, 1686, 1, 0, 0, 0, 1686, 1688, 1, 0, 0, 0, 1687, 1670, 1, 0, 0, 0, 1687, 1680, 1, 0, 0, 0, 1688, 447, 1, 0, 0, 0, 1689, 1692, 3, 446, 215, 0, 1690, 1692, 3, 298, 141, 0, 1691, 1689, 1, 0, 0, 0, 1691, 1690, 1, 0, 0, 0, 1692, 1693, 1, 0, 0, 0, 1693, 1691, 1, 0, 0, 0, 1693, 1694, 1, 0, 0, 0, 1694, 449, 1, 0, 0, 0, 1695, 1696, 3, 16, 0, 0, 1696, 1697, 1, 0, 0, 0, 1697, 1698, 6, 217, 0, 0, 1698, 451, 1, 0, 0, 0, 1699, 1700, 3, 18, 1, 0, 1700, 1701, 1, 0, 0, 0, 1701, 1702, 6, 218, 0, 0, 1702, 453, 1, 0, 0, 0, 1703, 1704, 3, 20, 2, 0, 1704, 1705, 1, 0, 0, 0, 1705, 1706, 6, 219, 0, 0, 1706, 455, 1, 0, 0, 0, 1707, 1708, 3, 174, 79, 0, 1708, 1709, 1, 0, 0, 0, 1709, 1710, 6, 220, 13, 0, 1710, 1711, 6, 220, 14, 0, 1711, 457, 1, 0, 0, 0, 1712, 1713, 3, 208, 96, 0, 1713, 1714, 1, 0, 0, 0, 1714, 1715, 6, 221, 26, 0, 1715, 459, 1, 0, 0, 0, 1716, 1717, 3, 216, 100, 0, 1717, 1718, 1, 0, 0, 0, 1718, 1719, 6, 222, 18, 0, 1719, 461, 1, 0, 0, 0, 1720, 1721, 3, 220, 102, 0, 1721, 1722, 1, 0, 0, 0, 1722, 1723, 6, 223, 17, 0, 1723, 463, 1, 0, 0, 0, 1724, 1725, 3, 244, 114, 0, 1725, 1726, 1, 0, 0, 0, 1726, 1727, 6, 224, 28, 0, 1727, 465, 1, 0, 0, 0, 1728, 1729, 3, 284, 134, 0, 1729, 1730, 1, 0, 0, 0, 1730, 1731, 6, 225, 29, 0, 1731, 467, 1, 0, 0, 0, 1732, 1733, 3, 280, 132, 0, 1733, 1734, 1, 0, 0, 0, 1734, 1735, 6, 226, 30, 0, 1735, 469, 1, 0, 0, 0, 1736, 1737, 3, 286, 135, 0, 1737, 1738, 1, 0, 0, 0, 1738, 1739, 6, 227, 31, 0, 1739, 471, 1, 0, 0, 0, 1740, 1741, 3, 204, 94, 0, 1741, 1742, 1, 0, 0, 0, 1742, 1743, 6, 228, 16, 0, 1743, 473, 1, 0, 0, 0, 1744, 1745, 3, 448, 216, 0, 1745, 1746, 1, 0, 0, 0, 1746, 1747, 6, 229, 27, 0, 1747, 475, 1, 0, 0, 0, 1748, 1749, 3, 16, 0, 0, 1749, 1750, 1, 0, 0, 0, 1750, 1751, 6, 230, 0, 0, 1751, 477, 1, 0, 0, 0, 1752, 1753, 3, 18, 1, 0, 1753, 1754, 1, 0, 0, 0, 1754, 1755, 6, 231, 0, 0, 1755, 479, 1, 0, 0, 0, 1756, 1757, 3, 20, 2, 0, 1757, 1758, 1, 0, 0, 0, 1758, 1759, 6, 232, 0, 0, 1759, 481, 1, 0, 0, 0, 1760, 1761, 3, 174, 79, 0, 1761, 1762, 1, 0, 0, 0, 1762, 1763, 6, 233, 13, 0, 1763, 1764, 6, 233, 14, 0, 1764, 483, 1, 0, 0, 0, 1765, 1766, 7, 10, 0, 0, 1766, 1767, 7, 5, 0, 0, 1767, 1768, 7, 21, 0, 0, 1768, 1769, 7, 9, 0, 0, 1769, 485, 1, 0, 0, 0, 1770, 1771, 3, 16, 0, 0, 1771, 1772, 1, 0, 0, 0, 1772, 1773, 6, 235, 0, 0, 1773, 487, 1, 0, 0, 0, 1774, 1775, 3, 18, 1, 0, 1775, 1776, 1, 0, 0, 0, 1776, 1777, 6, 236, 0, 0, 1777, 489, 1, 0, 0, 0, 1778, 1779, 3, 20, 2, 0, 1779, 1780, 1, 0, 0, 0, 1780, 1781, 6, 237, 0, 0, 1781, 491, 1, 0, 0, 0, 70, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 498, 502, 505, 514, 516, 527, 805, 875, 879, 884, 974, 976, 1027, 1032, 1041, 1048, 1053, 1055, 1066, 1074, 1077, 1079, 1084, 1089, 1095, 1102, 1107, 1113, 1116, 1124, 1128, 1270, 1275, 1282, 1284, 1289, 1294, 1301, 1303, 1329, 1334, 1339, 1341, 1347, 1406, 1411, 1666, 1670, 1675, 1680, 1685, 1687, 1691, 1693, 41, 0, 1, 0, 5, 1, 0, 5, 2, 0, 5, 5, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 5, 9, 0, 5, 10, 0, 5, 12, 0, 5, 13, 0, 5, 14, 0, 5, 15, 0, 7, 52, 0, 4, 0, 0, 7, 75, 0, 7, 57, 0, 7, 65, 0, 7, 63, 0, 7, 103, 0, 7, 102, 0, 7, 98, 0, 5, 4, 0, 5, 3, 0, 7, 80, 0, 7, 38, 0, 7, 59, 0, 7, 129, 0, 7, 77, 0, 7, 96, 0, 7, 95, 0, 7, 97, 0, 7, 99, 0, 7, 62, 0, 5, 0, 0, 7, 15, 0, 7, 61, 0, 7, 108, 0, 7, 53, 0, 7, 100, 0, 5, 11, 0] \ No newline at end of file +[4, 0, 139, 1776, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 2, 190, 7, 190, 2, 191, 7, 191, 2, 192, 7, 192, 2, 193, 7, 193, 2, 194, 7, 194, 2, 195, 7, 195, 2, 196, 7, 196, 2, 197, 7, 197, 2, 198, 7, 198, 2, 199, 7, 199, 2, 200, 7, 200, 2, 201, 7, 201, 2, 202, 7, 202, 2, 203, 7, 203, 2, 204, 7, 204, 2, 205, 7, 205, 2, 206, 7, 206, 2, 207, 7, 207, 2, 208, 7, 208, 2, 209, 7, 209, 2, 210, 7, 210, 2, 211, 7, 211, 2, 212, 7, 212, 2, 213, 7, 213, 2, 214, 7, 214, 2, 215, 7, 215, 2, 216, 7, 216, 2, 217, 7, 217, 2, 218, 7, 218, 2, 219, 7, 219, 2, 220, 7, 220, 2, 221, 7, 221, 2, 222, 7, 222, 2, 223, 7, 223, 2, 224, 7, 224, 2, 225, 7, 225, 2, 226, 7, 226, 2, 227, 7, 227, 2, 228, 7, 228, 2, 229, 7, 229, 2, 230, 7, 230, 2, 231, 7, 231, 2, 232, 7, 232, 2, 233, 7, 233, 2, 234, 7, 234, 2, 235, 7, 235, 2, 236, 7, 236, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 495, 8, 0, 10, 0, 12, 0, 498, 9, 0, 1, 0, 3, 0, 501, 8, 0, 1, 0, 3, 0, 504, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 513, 8, 1, 10, 1, 12, 1, 516, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 524, 8, 2, 11, 2, 12, 2, 525, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 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, 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, 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, 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, 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, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 4, 33, 802, 8, 33, 11, 33, 12, 33, 803, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 4, 49, 872, 8, 49, 11, 49, 12, 49, 873, 1, 49, 1, 49, 3, 49, 878, 8, 49, 1, 49, 4, 49, 881, 8, 49, 11, 49, 12, 49, 882, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 70, 4, 70, 973, 8, 70, 11, 70, 12, 70, 974, 1, 71, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 1, 84, 1, 84, 3, 84, 1026, 8, 84, 1, 84, 4, 84, 1029, 8, 84, 11, 84, 12, 84, 1030, 1, 85, 1, 85, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 3, 87, 1040, 8, 87, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 3, 89, 1047, 8, 89, 1, 90, 1, 90, 1, 90, 5, 90, 1052, 8, 90, 10, 90, 12, 90, 1055, 9, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 5, 90, 1063, 8, 90, 10, 90, 12, 90, 1066, 9, 90, 1, 90, 1, 90, 1, 90, 1, 90, 1, 90, 3, 90, 1073, 8, 90, 1, 90, 3, 90, 1076, 8, 90, 3, 90, 1078, 8, 90, 1, 91, 4, 91, 1081, 8, 91, 11, 91, 12, 91, 1082, 1, 92, 4, 92, 1086, 8, 92, 11, 92, 12, 92, 1087, 1, 92, 1, 92, 5, 92, 1092, 8, 92, 10, 92, 12, 92, 1095, 9, 92, 1, 92, 1, 92, 4, 92, 1099, 8, 92, 11, 92, 12, 92, 1100, 1, 92, 4, 92, 1104, 8, 92, 11, 92, 12, 92, 1105, 1, 92, 1, 92, 5, 92, 1110, 8, 92, 10, 92, 12, 92, 1113, 9, 92, 3, 92, 1115, 8, 92, 1, 92, 1, 92, 1, 92, 1, 92, 4, 92, 1121, 8, 92, 11, 92, 12, 92, 1122, 1, 92, 1, 92, 3, 92, 1127, 8, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 103, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 125, 1, 125, 1, 126, 1, 126, 1, 127, 1, 127, 1, 128, 1, 128, 1, 129, 1, 129, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 3, 133, 1266, 8, 133, 1, 133, 5, 133, 1269, 8, 133, 10, 133, 12, 133, 1272, 9, 133, 1, 133, 1, 133, 4, 133, 1276, 8, 133, 11, 133, 12, 133, 1277, 3, 133, 1280, 8, 133, 1, 134, 1, 134, 1, 134, 3, 134, 1285, 8, 134, 1, 134, 5, 134, 1288, 8, 134, 10, 134, 12, 134, 1291, 9, 134, 1, 134, 1, 134, 4, 134, 1295, 8, 134, 11, 134, 12, 134, 1296, 3, 134, 1299, 8, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 5, 139, 1323, 8, 139, 10, 139, 12, 139, 1326, 9, 139, 1, 139, 1, 139, 3, 139, 1330, 8, 139, 1, 139, 4, 139, 1333, 8, 139, 11, 139, 12, 139, 1334, 3, 139, 1337, 8, 139, 1, 140, 1, 140, 4, 140, 1341, 8, 140, 11, 140, 12, 140, 1342, 1, 140, 1, 140, 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, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 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, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 3, 153, 1402, 8, 153, 1, 154, 4, 154, 1405, 8, 154, 11, 154, 12, 154, 1406, 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, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 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, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 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, 169, 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, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 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, 193, 1, 193, 1, 193, 1, 193, 1, 194, 1, 194, 1, 194, 1, 194, 1, 195, 1, 195, 1, 195, 1, 195, 1, 195, 1, 196, 1, 196, 1, 196, 1, 196, 1, 197, 1, 197, 1, 197, 1, 197, 1, 198, 1, 198, 1, 198, 1, 198, 1, 199, 1, 199, 1, 199, 1, 199, 1, 200, 1, 200, 1, 200, 1, 200, 1, 201, 1, 201, 1, 201, 1, 201, 1, 202, 1, 202, 1, 202, 1, 202, 1, 203, 1, 203, 1, 203, 1, 203, 1, 204, 1, 204, 1, 204, 1, 204, 1, 205, 1, 205, 1, 205, 1, 205, 1, 206, 1, 206, 1, 206, 1, 206, 1, 206, 1, 207, 1, 207, 1, 207, 1, 207, 1, 208, 1, 208, 1, 208, 1, 208, 1, 209, 1, 209, 1, 209, 1, 209, 1, 210, 1, 210, 1, 210, 1, 210, 1, 211, 1, 211, 1, 211, 1, 211, 1, 212, 1, 212, 1, 212, 1, 212, 1, 213, 1, 213, 1, 213, 1, 213, 3, 213, 1662, 8, 213, 1, 214, 1, 214, 3, 214, 1666, 8, 214, 1, 214, 5, 214, 1669, 8, 214, 10, 214, 12, 214, 1672, 9, 214, 1, 214, 1, 214, 3, 214, 1676, 8, 214, 1, 214, 4, 214, 1679, 8, 214, 11, 214, 12, 214, 1680, 3, 214, 1683, 8, 214, 1, 215, 1, 215, 4, 215, 1687, 8, 215, 11, 215, 12, 215, 1688, 1, 216, 1, 216, 1, 216, 1, 216, 1, 217, 1, 217, 1, 217, 1, 217, 1, 218, 1, 218, 1, 218, 1, 218, 1, 219, 1, 219, 1, 219, 1, 219, 1, 219, 1, 220, 1, 220, 1, 220, 1, 220, 1, 221, 1, 221, 1, 221, 1, 221, 1, 222, 1, 222, 1, 222, 1, 222, 1, 223, 1, 223, 1, 223, 1, 223, 1, 224, 1, 224, 1, 224, 1, 224, 1, 225, 1, 225, 1, 225, 1, 225, 1, 226, 1, 226, 1, 226, 1, 226, 1, 227, 1, 227, 1, 227, 1, 228, 1, 228, 1, 228, 1, 228, 1, 229, 1, 229, 1, 229, 1, 229, 1, 230, 1, 230, 1, 230, 1, 230, 1, 231, 1, 231, 1, 231, 1, 231, 1, 232, 1, 232, 1, 232, 1, 232, 1, 232, 1, 233, 1, 233, 1, 233, 1, 233, 1, 233, 1, 234, 1, 234, 1, 234, 1, 234, 1, 235, 1, 235, 1, 235, 1, 235, 1, 236, 1, 236, 1, 236, 1, 236, 2, 514, 1064, 0, 237, 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, 27, 70, 28, 72, 29, 74, 30, 76, 31, 78, 32, 80, 33, 82, 34, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 0, 98, 35, 100, 36, 102, 37, 104, 0, 106, 0, 108, 0, 110, 0, 112, 0, 114, 38, 116, 0, 118, 39, 120, 40, 122, 41, 124, 0, 126, 0, 128, 0, 130, 0, 132, 0, 134, 0, 136, 0, 138, 0, 140, 0, 142, 0, 144, 0, 146, 42, 148, 43, 150, 44, 152, 0, 154, 0, 156, 45, 158, 46, 160, 47, 162, 48, 164, 0, 166, 0, 168, 49, 170, 50, 172, 51, 174, 52, 176, 0, 178, 0, 180, 0, 182, 0, 184, 0, 186, 0, 188, 0, 190, 0, 192, 0, 194, 0, 196, 53, 198, 54, 200, 55, 202, 56, 204, 57, 206, 58, 208, 59, 210, 60, 212, 61, 214, 62, 216, 63, 218, 64, 220, 65, 222, 66, 224, 67, 226, 68, 228, 69, 230, 70, 232, 71, 234, 72, 236, 73, 238, 74, 240, 75, 242, 76, 244, 77, 246, 78, 248, 79, 250, 80, 252, 81, 254, 82, 256, 83, 258, 84, 260, 85, 262, 86, 264, 87, 266, 88, 268, 89, 270, 90, 272, 91, 274, 92, 276, 93, 278, 94, 280, 0, 282, 95, 284, 96, 286, 97, 288, 98, 290, 99, 292, 100, 294, 101, 296, 0, 298, 102, 300, 103, 302, 104, 304, 105, 306, 0, 308, 0, 310, 0, 312, 0, 314, 0, 316, 0, 318, 0, 320, 106, 322, 0, 324, 107, 326, 0, 328, 0, 330, 108, 332, 109, 334, 110, 336, 0, 338, 0, 340, 111, 342, 112, 344, 113, 346, 0, 348, 114, 350, 0, 352, 0, 354, 115, 356, 0, 358, 0, 360, 0, 362, 0, 364, 0, 366, 116, 368, 117, 370, 118, 372, 0, 374, 0, 376, 0, 378, 0, 380, 0, 382, 0, 384, 0, 386, 119, 388, 120, 390, 121, 392, 0, 394, 0, 396, 0, 398, 0, 400, 122, 402, 123, 404, 124, 406, 0, 408, 0, 410, 0, 412, 0, 414, 0, 416, 0, 418, 0, 420, 0, 422, 125, 424, 126, 426, 127, 428, 0, 430, 0, 432, 0, 434, 0, 436, 0, 438, 0, 440, 0, 442, 0, 444, 0, 446, 128, 448, 129, 450, 130, 452, 131, 454, 0, 456, 0, 458, 0, 460, 0, 462, 0, 464, 0, 466, 0, 468, 0, 470, 132, 472, 0, 474, 133, 476, 134, 478, 135, 480, 0, 482, 136, 484, 137, 486, 138, 488, 139, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 36, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 65, 65, 97, 97, 2, 0, 78, 78, 110, 110, 2, 0, 71, 71, 103, 103, 2, 0, 69, 69, 101, 101, 2, 0, 80, 80, 112, 112, 2, 0, 79, 79, 111, 111, 2, 0, 73, 73, 105, 105, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 88, 88, 120, 120, 2, 0, 76, 76, 108, 108, 2, 0, 77, 77, 109, 109, 2, 0, 68, 68, 100, 100, 2, 0, 83, 83, 115, 115, 2, 0, 86, 86, 118, 118, 2, 0, 75, 75, 107, 107, 2, 0, 87, 87, 119, 119, 2, 0, 70, 70, 102, 102, 2, 0, 85, 85, 117, 117, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 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, 2, 0, 74, 74, 106, 106, 1807, 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, 0, 66, 1, 0, 0, 0, 0, 68, 1, 0, 0, 0, 0, 70, 1, 0, 0, 0, 0, 72, 1, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 78, 1, 0, 0, 0, 0, 80, 1, 0, 0, 0, 0, 82, 1, 0, 0, 0, 1, 84, 1, 0, 0, 0, 1, 86, 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, 2, 104, 1, 0, 0, 0, 2, 106, 1, 0, 0, 0, 2, 108, 1, 0, 0, 0, 2, 110, 1, 0, 0, 0, 2, 114, 1, 0, 0, 0, 2, 116, 1, 0, 0, 0, 2, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 3, 124, 1, 0, 0, 0, 3, 126, 1, 0, 0, 0, 3, 128, 1, 0, 0, 0, 3, 130, 1, 0, 0, 0, 3, 132, 1, 0, 0, 0, 3, 134, 1, 0, 0, 0, 3, 136, 1, 0, 0, 0, 3, 138, 1, 0, 0, 0, 3, 140, 1, 0, 0, 0, 3, 142, 1, 0, 0, 0, 3, 144, 1, 0, 0, 0, 3, 146, 1, 0, 0, 0, 3, 148, 1, 0, 0, 0, 3, 150, 1, 0, 0, 0, 4, 152, 1, 0, 0, 0, 4, 154, 1, 0, 0, 0, 4, 156, 1, 0, 0, 0, 4, 158, 1, 0, 0, 0, 4, 160, 1, 0, 0, 0, 4, 162, 1, 0, 0, 0, 5, 164, 1, 0, 0, 0, 5, 166, 1, 0, 0, 0, 5, 168, 1, 0, 0, 0, 5, 170, 1, 0, 0, 0, 5, 172, 1, 0, 0, 0, 6, 174, 1, 0, 0, 0, 6, 196, 1, 0, 0, 0, 6, 198, 1, 0, 0, 0, 6, 200, 1, 0, 0, 0, 6, 202, 1, 0, 0, 0, 6, 204, 1, 0, 0, 0, 6, 206, 1, 0, 0, 0, 6, 208, 1, 0, 0, 0, 6, 210, 1, 0, 0, 0, 6, 212, 1, 0, 0, 0, 6, 214, 1, 0, 0, 0, 6, 216, 1, 0, 0, 0, 6, 218, 1, 0, 0, 0, 6, 220, 1, 0, 0, 0, 6, 222, 1, 0, 0, 0, 6, 224, 1, 0, 0, 0, 6, 226, 1, 0, 0, 0, 6, 228, 1, 0, 0, 0, 6, 230, 1, 0, 0, 0, 6, 232, 1, 0, 0, 0, 6, 234, 1, 0, 0, 0, 6, 236, 1, 0, 0, 0, 6, 238, 1, 0, 0, 0, 6, 240, 1, 0, 0, 0, 6, 242, 1, 0, 0, 0, 6, 244, 1, 0, 0, 0, 6, 246, 1, 0, 0, 0, 6, 248, 1, 0, 0, 0, 6, 250, 1, 0, 0, 0, 6, 252, 1, 0, 0, 0, 6, 254, 1, 0, 0, 0, 6, 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, 266, 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, 6, 278, 1, 0, 0, 0, 6, 280, 1, 0, 0, 0, 6, 282, 1, 0, 0, 0, 6, 284, 1, 0, 0, 0, 6, 286, 1, 0, 0, 0, 6, 288, 1, 0, 0, 0, 6, 290, 1, 0, 0, 0, 6, 292, 1, 0, 0, 0, 6, 294, 1, 0, 0, 0, 6, 298, 1, 0, 0, 0, 6, 300, 1, 0, 0, 0, 6, 302, 1, 0, 0, 0, 6, 304, 1, 0, 0, 0, 7, 306, 1, 0, 0, 0, 7, 308, 1, 0, 0, 0, 7, 310, 1, 0, 0, 0, 7, 312, 1, 0, 0, 0, 7, 314, 1, 0, 0, 0, 7, 316, 1, 0, 0, 0, 7, 318, 1, 0, 0, 0, 7, 320, 1, 0, 0, 0, 7, 324, 1, 0, 0, 0, 7, 326, 1, 0, 0, 0, 7, 328, 1, 0, 0, 0, 7, 330, 1, 0, 0, 0, 7, 332, 1, 0, 0, 0, 7, 334, 1, 0, 0, 0, 8, 336, 1, 0, 0, 0, 8, 338, 1, 0, 0, 0, 8, 340, 1, 0, 0, 0, 8, 342, 1, 0, 0, 0, 8, 344, 1, 0, 0, 0, 9, 346, 1, 0, 0, 0, 9, 348, 1, 0, 0, 0, 9, 350, 1, 0, 0, 0, 9, 352, 1, 0, 0, 0, 9, 354, 1, 0, 0, 0, 9, 356, 1, 0, 0, 0, 9, 358, 1, 0, 0, 0, 9, 360, 1, 0, 0, 0, 9, 362, 1, 0, 0, 0, 9, 364, 1, 0, 0, 0, 9, 366, 1, 0, 0, 0, 9, 368, 1, 0, 0, 0, 9, 370, 1, 0, 0, 0, 10, 372, 1, 0, 0, 0, 10, 374, 1, 0, 0, 0, 10, 376, 1, 0, 0, 0, 10, 378, 1, 0, 0, 0, 10, 380, 1, 0, 0, 0, 10, 382, 1, 0, 0, 0, 10, 384, 1, 0, 0, 0, 10, 386, 1, 0, 0, 0, 10, 388, 1, 0, 0, 0, 10, 390, 1, 0, 0, 0, 11, 392, 1, 0, 0, 0, 11, 394, 1, 0, 0, 0, 11, 396, 1, 0, 0, 0, 11, 398, 1, 0, 0, 0, 11, 400, 1, 0, 0, 0, 11, 402, 1, 0, 0, 0, 11, 404, 1, 0, 0, 0, 12, 406, 1, 0, 0, 0, 12, 408, 1, 0, 0, 0, 12, 410, 1, 0, 0, 0, 12, 412, 1, 0, 0, 0, 12, 414, 1, 0, 0, 0, 12, 416, 1, 0, 0, 0, 12, 418, 1, 0, 0, 0, 12, 420, 1, 0, 0, 0, 12, 422, 1, 0, 0, 0, 12, 424, 1, 0, 0, 0, 12, 426, 1, 0, 0, 0, 13, 428, 1, 0, 0, 0, 13, 430, 1, 0, 0, 0, 13, 432, 1, 0, 0, 0, 13, 434, 1, 0, 0, 0, 13, 436, 1, 0, 0, 0, 13, 438, 1, 0, 0, 0, 13, 440, 1, 0, 0, 0, 13, 446, 1, 0, 0, 0, 13, 448, 1, 0, 0, 0, 13, 450, 1, 0, 0, 0, 13, 452, 1, 0, 0, 0, 14, 454, 1, 0, 0, 0, 14, 456, 1, 0, 0, 0, 14, 458, 1, 0, 0, 0, 14, 460, 1, 0, 0, 0, 14, 462, 1, 0, 0, 0, 14, 464, 1, 0, 0, 0, 14, 466, 1, 0, 0, 0, 14, 468, 1, 0, 0, 0, 14, 470, 1, 0, 0, 0, 14, 472, 1, 0, 0, 0, 14, 474, 1, 0, 0, 0, 14, 476, 1, 0, 0, 0, 14, 478, 1, 0, 0, 0, 15, 480, 1, 0, 0, 0, 15, 482, 1, 0, 0, 0, 15, 484, 1, 0, 0, 0, 15, 486, 1, 0, 0, 0, 15, 488, 1, 0, 0, 0, 16, 490, 1, 0, 0, 0, 18, 507, 1, 0, 0, 0, 20, 523, 1, 0, 0, 0, 22, 529, 1, 0, 0, 0, 24, 544, 1, 0, 0, 0, 26, 553, 1, 0, 0, 0, 28, 563, 1, 0, 0, 0, 30, 576, 1, 0, 0, 0, 32, 586, 1, 0, 0, 0, 34, 593, 1, 0, 0, 0, 36, 600, 1, 0, 0, 0, 38, 608, 1, 0, 0, 0, 40, 614, 1, 0, 0, 0, 42, 621, 1, 0, 0, 0, 44, 629, 1, 0, 0, 0, 46, 637, 1, 0, 0, 0, 48, 652, 1, 0, 0, 0, 50, 662, 1, 0, 0, 0, 52, 672, 1, 0, 0, 0, 54, 679, 1, 0, 0, 0, 56, 685, 1, 0, 0, 0, 58, 693, 1, 0, 0, 0, 60, 702, 1, 0, 0, 0, 62, 710, 1, 0, 0, 0, 64, 718, 1, 0, 0, 0, 66, 727, 1, 0, 0, 0, 68, 739, 1, 0, 0, 0, 70, 751, 1, 0, 0, 0, 72, 758, 1, 0, 0, 0, 74, 765, 1, 0, 0, 0, 76, 777, 1, 0, 0, 0, 78, 784, 1, 0, 0, 0, 80, 793, 1, 0, 0, 0, 82, 801, 1, 0, 0, 0, 84, 807, 1, 0, 0, 0, 86, 812, 1, 0, 0, 0, 88, 816, 1, 0, 0, 0, 90, 820, 1, 0, 0, 0, 92, 824, 1, 0, 0, 0, 94, 828, 1, 0, 0, 0, 96, 832, 1, 0, 0, 0, 98, 836, 1, 0, 0, 0, 100, 840, 1, 0, 0, 0, 102, 844, 1, 0, 0, 0, 104, 848, 1, 0, 0, 0, 106, 853, 1, 0, 0, 0, 108, 858, 1, 0, 0, 0, 110, 863, 1, 0, 0, 0, 112, 868, 1, 0, 0, 0, 114, 877, 1, 0, 0, 0, 116, 884, 1, 0, 0, 0, 118, 888, 1, 0, 0, 0, 120, 892, 1, 0, 0, 0, 122, 896, 1, 0, 0, 0, 124, 900, 1, 0, 0, 0, 126, 906, 1, 0, 0, 0, 128, 910, 1, 0, 0, 0, 130, 914, 1, 0, 0, 0, 132, 918, 1, 0, 0, 0, 134, 922, 1, 0, 0, 0, 136, 926, 1, 0, 0, 0, 138, 930, 1, 0, 0, 0, 140, 934, 1, 0, 0, 0, 142, 938, 1, 0, 0, 0, 144, 942, 1, 0, 0, 0, 146, 946, 1, 0, 0, 0, 148, 950, 1, 0, 0, 0, 150, 954, 1, 0, 0, 0, 152, 958, 1, 0, 0, 0, 154, 963, 1, 0, 0, 0, 156, 972, 1, 0, 0, 0, 158, 976, 1, 0, 0, 0, 160, 980, 1, 0, 0, 0, 162, 984, 1, 0, 0, 0, 164, 988, 1, 0, 0, 0, 166, 993, 1, 0, 0, 0, 168, 998, 1, 0, 0, 0, 170, 1002, 1, 0, 0, 0, 172, 1006, 1, 0, 0, 0, 174, 1010, 1, 0, 0, 0, 176, 1014, 1, 0, 0, 0, 178, 1016, 1, 0, 0, 0, 180, 1018, 1, 0, 0, 0, 182, 1021, 1, 0, 0, 0, 184, 1023, 1, 0, 0, 0, 186, 1032, 1, 0, 0, 0, 188, 1034, 1, 0, 0, 0, 190, 1039, 1, 0, 0, 0, 192, 1041, 1, 0, 0, 0, 194, 1046, 1, 0, 0, 0, 196, 1077, 1, 0, 0, 0, 198, 1080, 1, 0, 0, 0, 200, 1126, 1, 0, 0, 0, 202, 1128, 1, 0, 0, 0, 204, 1132, 1, 0, 0, 0, 206, 1136, 1, 0, 0, 0, 208, 1138, 1, 0, 0, 0, 210, 1141, 1, 0, 0, 0, 212, 1144, 1, 0, 0, 0, 214, 1146, 1, 0, 0, 0, 216, 1148, 1, 0, 0, 0, 218, 1153, 1, 0, 0, 0, 220, 1155, 1, 0, 0, 0, 222, 1161, 1, 0, 0, 0, 224, 1167, 1, 0, 0, 0, 226, 1170, 1, 0, 0, 0, 228, 1173, 1, 0, 0, 0, 230, 1178, 1, 0, 0, 0, 232, 1183, 1, 0, 0, 0, 234, 1187, 1, 0, 0, 0, 236, 1192, 1, 0, 0, 0, 238, 1198, 1, 0, 0, 0, 240, 1201, 1, 0, 0, 0, 242, 1204, 1, 0, 0, 0, 244, 1206, 1, 0, 0, 0, 246, 1212, 1, 0, 0, 0, 248, 1217, 1, 0, 0, 0, 250, 1222, 1, 0, 0, 0, 252, 1225, 1, 0, 0, 0, 254, 1228, 1, 0, 0, 0, 256, 1231, 1, 0, 0, 0, 258, 1233, 1, 0, 0, 0, 260, 1236, 1, 0, 0, 0, 262, 1238, 1, 0, 0, 0, 264, 1241, 1, 0, 0, 0, 266, 1243, 1, 0, 0, 0, 268, 1245, 1, 0, 0, 0, 270, 1247, 1, 0, 0, 0, 272, 1249, 1, 0, 0, 0, 274, 1251, 1, 0, 0, 0, 276, 1253, 1, 0, 0, 0, 278, 1255, 1, 0, 0, 0, 280, 1258, 1, 0, 0, 0, 282, 1279, 1, 0, 0, 0, 284, 1298, 1, 0, 0, 0, 286, 1300, 1, 0, 0, 0, 288, 1305, 1, 0, 0, 0, 290, 1310, 1, 0, 0, 0, 292, 1315, 1, 0, 0, 0, 294, 1336, 1, 0, 0, 0, 296, 1338, 1, 0, 0, 0, 298, 1346, 1, 0, 0, 0, 300, 1348, 1, 0, 0, 0, 302, 1352, 1, 0, 0, 0, 304, 1356, 1, 0, 0, 0, 306, 1360, 1, 0, 0, 0, 308, 1365, 1, 0, 0, 0, 310, 1369, 1, 0, 0, 0, 312, 1373, 1, 0, 0, 0, 314, 1377, 1, 0, 0, 0, 316, 1381, 1, 0, 0, 0, 318, 1385, 1, 0, 0, 0, 320, 1389, 1, 0, 0, 0, 322, 1401, 1, 0, 0, 0, 324, 1404, 1, 0, 0, 0, 326, 1408, 1, 0, 0, 0, 328, 1412, 1, 0, 0, 0, 330, 1416, 1, 0, 0, 0, 332, 1420, 1, 0, 0, 0, 334, 1424, 1, 0, 0, 0, 336, 1428, 1, 0, 0, 0, 338, 1433, 1, 0, 0, 0, 340, 1438, 1, 0, 0, 0, 342, 1442, 1, 0, 0, 0, 344, 1446, 1, 0, 0, 0, 346, 1450, 1, 0, 0, 0, 348, 1455, 1, 0, 0, 0, 350, 1460, 1, 0, 0, 0, 352, 1464, 1, 0, 0, 0, 354, 1470, 1, 0, 0, 0, 356, 1479, 1, 0, 0, 0, 358, 1483, 1, 0, 0, 0, 360, 1487, 1, 0, 0, 0, 362, 1491, 1, 0, 0, 0, 364, 1495, 1, 0, 0, 0, 366, 1499, 1, 0, 0, 0, 368, 1503, 1, 0, 0, 0, 370, 1507, 1, 0, 0, 0, 372, 1511, 1, 0, 0, 0, 374, 1516, 1, 0, 0, 0, 376, 1520, 1, 0, 0, 0, 378, 1524, 1, 0, 0, 0, 380, 1528, 1, 0, 0, 0, 382, 1533, 1, 0, 0, 0, 384, 1537, 1, 0, 0, 0, 386, 1541, 1, 0, 0, 0, 388, 1545, 1, 0, 0, 0, 390, 1549, 1, 0, 0, 0, 392, 1553, 1, 0, 0, 0, 394, 1559, 1, 0, 0, 0, 396, 1563, 1, 0, 0, 0, 398, 1567, 1, 0, 0, 0, 400, 1571, 1, 0, 0, 0, 402, 1575, 1, 0, 0, 0, 404, 1579, 1, 0, 0, 0, 406, 1583, 1, 0, 0, 0, 408, 1588, 1, 0, 0, 0, 410, 1592, 1, 0, 0, 0, 412, 1596, 1, 0, 0, 0, 414, 1600, 1, 0, 0, 0, 416, 1604, 1, 0, 0, 0, 418, 1608, 1, 0, 0, 0, 420, 1612, 1, 0, 0, 0, 422, 1616, 1, 0, 0, 0, 424, 1620, 1, 0, 0, 0, 426, 1624, 1, 0, 0, 0, 428, 1628, 1, 0, 0, 0, 430, 1633, 1, 0, 0, 0, 432, 1637, 1, 0, 0, 0, 434, 1641, 1, 0, 0, 0, 436, 1645, 1, 0, 0, 0, 438, 1649, 1, 0, 0, 0, 440, 1653, 1, 0, 0, 0, 442, 1661, 1, 0, 0, 0, 444, 1682, 1, 0, 0, 0, 446, 1686, 1, 0, 0, 0, 448, 1690, 1, 0, 0, 0, 450, 1694, 1, 0, 0, 0, 452, 1698, 1, 0, 0, 0, 454, 1702, 1, 0, 0, 0, 456, 1707, 1, 0, 0, 0, 458, 1711, 1, 0, 0, 0, 460, 1715, 1, 0, 0, 0, 462, 1719, 1, 0, 0, 0, 464, 1723, 1, 0, 0, 0, 466, 1727, 1, 0, 0, 0, 468, 1731, 1, 0, 0, 0, 470, 1735, 1, 0, 0, 0, 472, 1738, 1, 0, 0, 0, 474, 1742, 1, 0, 0, 0, 476, 1746, 1, 0, 0, 0, 478, 1750, 1, 0, 0, 0, 480, 1754, 1, 0, 0, 0, 482, 1759, 1, 0, 0, 0, 484, 1764, 1, 0, 0, 0, 486, 1768, 1, 0, 0, 0, 488, 1772, 1, 0, 0, 0, 490, 491, 5, 47, 0, 0, 491, 492, 5, 47, 0, 0, 492, 496, 1, 0, 0, 0, 493, 495, 8, 0, 0, 0, 494, 493, 1, 0, 0, 0, 495, 498, 1, 0, 0, 0, 496, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 500, 1, 0, 0, 0, 498, 496, 1, 0, 0, 0, 499, 501, 5, 13, 0, 0, 500, 499, 1, 0, 0, 0, 500, 501, 1, 0, 0, 0, 501, 503, 1, 0, 0, 0, 502, 504, 5, 10, 0, 0, 503, 502, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 1, 0, 0, 0, 505, 506, 6, 0, 0, 0, 506, 17, 1, 0, 0, 0, 507, 508, 5, 47, 0, 0, 508, 509, 5, 42, 0, 0, 509, 514, 1, 0, 0, 0, 510, 513, 3, 18, 1, 0, 511, 513, 9, 0, 0, 0, 512, 510, 1, 0, 0, 0, 512, 511, 1, 0, 0, 0, 513, 516, 1, 0, 0, 0, 514, 515, 1, 0, 0, 0, 514, 512, 1, 0, 0, 0, 515, 517, 1, 0, 0, 0, 516, 514, 1, 0, 0, 0, 517, 518, 5, 42, 0, 0, 518, 519, 5, 47, 0, 0, 519, 520, 1, 0, 0, 0, 520, 521, 6, 1, 0, 0, 521, 19, 1, 0, 0, 0, 522, 524, 7, 1, 0, 0, 523, 522, 1, 0, 0, 0, 524, 525, 1, 0, 0, 0, 525, 523, 1, 0, 0, 0, 525, 526, 1, 0, 0, 0, 526, 527, 1, 0, 0, 0, 527, 528, 6, 2, 0, 0, 528, 21, 1, 0, 0, 0, 529, 530, 7, 2, 0, 0, 530, 531, 7, 3, 0, 0, 531, 532, 7, 4, 0, 0, 532, 533, 7, 5, 0, 0, 533, 534, 7, 6, 0, 0, 534, 535, 7, 7, 0, 0, 535, 536, 5, 95, 0, 0, 536, 537, 7, 8, 0, 0, 537, 538, 7, 9, 0, 0, 538, 539, 7, 10, 0, 0, 539, 540, 7, 5, 0, 0, 540, 541, 7, 11, 0, 0, 541, 542, 1, 0, 0, 0, 542, 543, 6, 3, 1, 0, 543, 23, 1, 0, 0, 0, 544, 545, 7, 7, 0, 0, 545, 546, 7, 5, 0, 0, 546, 547, 7, 12, 0, 0, 547, 548, 7, 10, 0, 0, 548, 549, 7, 2, 0, 0, 549, 550, 7, 3, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 6, 4, 2, 0, 552, 25, 1, 0, 0, 0, 553, 554, 7, 7, 0, 0, 554, 555, 7, 13, 0, 0, 555, 556, 7, 8, 0, 0, 556, 557, 7, 14, 0, 0, 557, 558, 7, 4, 0, 0, 558, 559, 7, 10, 0, 0, 559, 560, 7, 5, 0, 0, 560, 561, 1, 0, 0, 0, 561, 562, 6, 5, 3, 0, 562, 27, 1, 0, 0, 0, 563, 564, 7, 2, 0, 0, 564, 565, 7, 9, 0, 0, 565, 566, 7, 15, 0, 0, 566, 567, 7, 8, 0, 0, 567, 568, 7, 14, 0, 0, 568, 569, 7, 7, 0, 0, 569, 570, 7, 11, 0, 0, 570, 571, 7, 10, 0, 0, 571, 572, 7, 9, 0, 0, 572, 573, 7, 5, 0, 0, 573, 574, 1, 0, 0, 0, 574, 575, 6, 6, 4, 0, 575, 29, 1, 0, 0, 0, 576, 577, 7, 16, 0, 0, 577, 578, 7, 10, 0, 0, 578, 579, 7, 17, 0, 0, 579, 580, 7, 17, 0, 0, 580, 581, 7, 7, 0, 0, 581, 582, 7, 2, 0, 0, 582, 583, 7, 11, 0, 0, 583, 584, 1, 0, 0, 0, 584, 585, 6, 7, 4, 0, 585, 31, 1, 0, 0, 0, 586, 587, 7, 7, 0, 0, 587, 588, 7, 18, 0, 0, 588, 589, 7, 4, 0, 0, 589, 590, 7, 14, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 6, 8, 4, 0, 592, 33, 1, 0, 0, 0, 593, 594, 7, 6, 0, 0, 594, 595, 7, 12, 0, 0, 595, 596, 7, 9, 0, 0, 596, 597, 7, 19, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 6, 9, 4, 0, 599, 35, 1, 0, 0, 0, 600, 601, 7, 14, 0, 0, 601, 602, 7, 10, 0, 0, 602, 603, 7, 15, 0, 0, 603, 604, 7, 10, 0, 0, 604, 605, 7, 11, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 6, 10, 4, 0, 607, 37, 1, 0, 0, 0, 608, 609, 7, 12, 0, 0, 609, 610, 7, 9, 0, 0, 610, 611, 7, 20, 0, 0, 611, 612, 1, 0, 0, 0, 612, 613, 6, 11, 4, 0, 613, 39, 1, 0, 0, 0, 614, 615, 7, 17, 0, 0, 615, 616, 7, 9, 0, 0, 616, 617, 7, 12, 0, 0, 617, 618, 7, 11, 0, 0, 618, 619, 1, 0, 0, 0, 619, 620, 6, 12, 4, 0, 620, 41, 1, 0, 0, 0, 621, 622, 7, 17, 0, 0, 622, 623, 7, 11, 0, 0, 623, 624, 7, 4, 0, 0, 624, 625, 7, 11, 0, 0, 625, 626, 7, 17, 0, 0, 626, 627, 1, 0, 0, 0, 627, 628, 6, 13, 4, 0, 628, 43, 1, 0, 0, 0, 629, 630, 7, 20, 0, 0, 630, 631, 7, 3, 0, 0, 631, 632, 7, 7, 0, 0, 632, 633, 7, 12, 0, 0, 633, 634, 7, 7, 0, 0, 634, 635, 1, 0, 0, 0, 635, 636, 6, 14, 4, 0, 636, 45, 1, 0, 0, 0, 637, 638, 4, 15, 0, 0, 638, 639, 7, 10, 0, 0, 639, 640, 7, 5, 0, 0, 640, 641, 7, 14, 0, 0, 641, 642, 7, 10, 0, 0, 642, 643, 7, 5, 0, 0, 643, 644, 7, 7, 0, 0, 644, 645, 7, 17, 0, 0, 645, 646, 7, 11, 0, 0, 646, 647, 7, 4, 0, 0, 647, 648, 7, 11, 0, 0, 648, 649, 7, 17, 0, 0, 649, 650, 1, 0, 0, 0, 650, 651, 6, 15, 4, 0, 651, 47, 1, 0, 0, 0, 652, 653, 4, 16, 1, 0, 653, 654, 7, 12, 0, 0, 654, 655, 7, 7, 0, 0, 655, 656, 7, 12, 0, 0, 656, 657, 7, 4, 0, 0, 657, 658, 7, 5, 0, 0, 658, 659, 7, 19, 0, 0, 659, 660, 1, 0, 0, 0, 660, 661, 6, 16, 4, 0, 661, 49, 1, 0, 0, 0, 662, 663, 4, 17, 2, 0, 663, 664, 7, 17, 0, 0, 664, 665, 7, 4, 0, 0, 665, 666, 7, 15, 0, 0, 666, 667, 7, 8, 0, 0, 667, 668, 7, 14, 0, 0, 668, 669, 7, 7, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 6, 17, 4, 0, 671, 51, 1, 0, 0, 0, 672, 673, 7, 21, 0, 0, 673, 674, 7, 12, 0, 0, 674, 675, 7, 9, 0, 0, 675, 676, 7, 15, 0, 0, 676, 677, 1, 0, 0, 0, 677, 678, 6, 18, 5, 0, 678, 53, 1, 0, 0, 0, 679, 680, 4, 19, 3, 0, 680, 681, 7, 11, 0, 0, 681, 682, 7, 17, 0, 0, 682, 683, 1, 0, 0, 0, 683, 684, 6, 19, 5, 0, 684, 55, 1, 0, 0, 0, 685, 686, 4, 20, 4, 0, 686, 687, 7, 21, 0, 0, 687, 688, 7, 9, 0, 0, 688, 689, 7, 12, 0, 0, 689, 690, 7, 19, 0, 0, 690, 691, 1, 0, 0, 0, 691, 692, 6, 20, 6, 0, 692, 57, 1, 0, 0, 0, 693, 694, 7, 14, 0, 0, 694, 695, 7, 9, 0, 0, 695, 696, 7, 9, 0, 0, 696, 697, 7, 19, 0, 0, 697, 698, 7, 22, 0, 0, 698, 699, 7, 8, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 6, 21, 7, 0, 701, 59, 1, 0, 0, 0, 702, 703, 4, 22, 5, 0, 703, 704, 7, 21, 0, 0, 704, 705, 7, 22, 0, 0, 705, 706, 7, 14, 0, 0, 706, 707, 7, 14, 0, 0, 707, 708, 1, 0, 0, 0, 708, 709, 6, 22, 7, 0, 709, 61, 1, 0, 0, 0, 710, 711, 4, 23, 6, 0, 711, 712, 7, 14, 0, 0, 712, 713, 7, 7, 0, 0, 713, 714, 7, 21, 0, 0, 714, 715, 7, 11, 0, 0, 715, 716, 1, 0, 0, 0, 716, 717, 6, 23, 7, 0, 717, 63, 1, 0, 0, 0, 718, 719, 4, 24, 7, 0, 719, 720, 7, 12, 0, 0, 720, 721, 7, 10, 0, 0, 721, 722, 7, 6, 0, 0, 722, 723, 7, 3, 0, 0, 723, 724, 7, 11, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 6, 24, 7, 0, 726, 65, 1, 0, 0, 0, 727, 728, 4, 25, 8, 0, 728, 729, 7, 14, 0, 0, 729, 730, 7, 9, 0, 0, 730, 731, 7, 9, 0, 0, 731, 732, 7, 19, 0, 0, 732, 733, 7, 22, 0, 0, 733, 734, 7, 8, 0, 0, 734, 735, 5, 95, 0, 0, 735, 736, 5, 128020, 0, 0, 736, 737, 1, 0, 0, 0, 737, 738, 6, 25, 8, 0, 738, 67, 1, 0, 0, 0, 739, 740, 7, 15, 0, 0, 740, 741, 7, 18, 0, 0, 741, 742, 5, 95, 0, 0, 742, 743, 7, 7, 0, 0, 743, 744, 7, 13, 0, 0, 744, 745, 7, 8, 0, 0, 745, 746, 7, 4, 0, 0, 746, 747, 7, 5, 0, 0, 747, 748, 7, 16, 0, 0, 748, 749, 1, 0, 0, 0, 749, 750, 6, 26, 9, 0, 750, 69, 1, 0, 0, 0, 751, 752, 7, 16, 0, 0, 752, 753, 7, 12, 0, 0, 753, 754, 7, 9, 0, 0, 754, 755, 7, 8, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 6, 27, 10, 0, 757, 71, 1, 0, 0, 0, 758, 759, 7, 19, 0, 0, 759, 760, 7, 7, 0, 0, 760, 761, 7, 7, 0, 0, 761, 762, 7, 8, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 6, 28, 10, 0, 764, 73, 1, 0, 0, 0, 765, 766, 4, 29, 9, 0, 766, 767, 7, 10, 0, 0, 767, 768, 7, 5, 0, 0, 768, 769, 7, 17, 0, 0, 769, 770, 7, 10, 0, 0, 770, 771, 7, 17, 0, 0, 771, 772, 7, 11, 0, 0, 772, 773, 5, 95, 0, 0, 773, 774, 5, 128020, 0, 0, 774, 775, 1, 0, 0, 0, 775, 776, 6, 29, 10, 0, 776, 75, 1, 0, 0, 0, 777, 778, 4, 30, 10, 0, 778, 779, 7, 12, 0, 0, 779, 780, 7, 12, 0, 0, 780, 781, 7, 21, 0, 0, 781, 782, 1, 0, 0, 0, 782, 783, 6, 30, 4, 0, 783, 77, 1, 0, 0, 0, 784, 785, 7, 12, 0, 0, 785, 786, 7, 7, 0, 0, 786, 787, 7, 5, 0, 0, 787, 788, 7, 4, 0, 0, 788, 789, 7, 15, 0, 0, 789, 790, 7, 7, 0, 0, 790, 791, 1, 0, 0, 0, 791, 792, 6, 31, 11, 0, 792, 79, 1, 0, 0, 0, 793, 794, 7, 17, 0, 0, 794, 795, 7, 3, 0, 0, 795, 796, 7, 9, 0, 0, 796, 797, 7, 20, 0, 0, 797, 798, 1, 0, 0, 0, 798, 799, 6, 32, 12, 0, 799, 81, 1, 0, 0, 0, 800, 802, 8, 23, 0, 0, 801, 800, 1, 0, 0, 0, 802, 803, 1, 0, 0, 0, 803, 801, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 806, 6, 33, 4, 0, 806, 83, 1, 0, 0, 0, 807, 808, 3, 174, 79, 0, 808, 809, 1, 0, 0, 0, 809, 810, 6, 34, 13, 0, 810, 811, 6, 34, 14, 0, 811, 85, 1, 0, 0, 0, 812, 813, 3, 238, 111, 0, 813, 814, 1, 0, 0, 0, 814, 815, 6, 35, 15, 0, 815, 87, 1, 0, 0, 0, 816, 817, 3, 470, 227, 0, 817, 818, 1, 0, 0, 0, 818, 819, 6, 36, 16, 0, 819, 89, 1, 0, 0, 0, 820, 821, 3, 218, 101, 0, 821, 822, 1, 0, 0, 0, 822, 823, 6, 37, 17, 0, 823, 91, 1, 0, 0, 0, 824, 825, 3, 214, 99, 0, 825, 826, 1, 0, 0, 0, 826, 827, 6, 38, 18, 0, 827, 93, 1, 0, 0, 0, 828, 829, 3, 298, 141, 0, 829, 830, 1, 0, 0, 0, 830, 831, 6, 39, 19, 0, 831, 95, 1, 0, 0, 0, 832, 833, 3, 294, 139, 0, 833, 834, 1, 0, 0, 0, 834, 835, 6, 40, 20, 0, 835, 97, 1, 0, 0, 0, 836, 837, 3, 16, 0, 0, 837, 838, 1, 0, 0, 0, 838, 839, 6, 41, 0, 0, 839, 99, 1, 0, 0, 0, 840, 841, 3, 18, 1, 0, 841, 842, 1, 0, 0, 0, 842, 843, 6, 42, 0, 0, 843, 101, 1, 0, 0, 0, 844, 845, 3, 20, 2, 0, 845, 846, 1, 0, 0, 0, 846, 847, 6, 43, 0, 0, 847, 103, 1, 0, 0, 0, 848, 849, 3, 174, 79, 0, 849, 850, 1, 0, 0, 0, 850, 851, 6, 44, 13, 0, 851, 852, 6, 44, 14, 0, 852, 105, 1, 0, 0, 0, 853, 854, 3, 286, 135, 0, 854, 855, 1, 0, 0, 0, 855, 856, 6, 45, 21, 0, 856, 857, 6, 45, 22, 0, 857, 107, 1, 0, 0, 0, 858, 859, 3, 238, 111, 0, 859, 860, 1, 0, 0, 0, 860, 861, 6, 46, 15, 0, 861, 862, 6, 46, 23, 0, 862, 109, 1, 0, 0, 0, 863, 864, 3, 248, 116, 0, 864, 865, 1, 0, 0, 0, 865, 866, 6, 47, 24, 0, 866, 867, 6, 47, 23, 0, 867, 111, 1, 0, 0, 0, 868, 869, 8, 24, 0, 0, 869, 113, 1, 0, 0, 0, 870, 872, 3, 112, 48, 0, 871, 870, 1, 0, 0, 0, 872, 873, 1, 0, 0, 0, 873, 871, 1, 0, 0, 0, 873, 874, 1, 0, 0, 0, 874, 875, 1, 0, 0, 0, 875, 876, 3, 212, 98, 0, 876, 878, 1, 0, 0, 0, 877, 871, 1, 0, 0, 0, 877, 878, 1, 0, 0, 0, 878, 880, 1, 0, 0, 0, 879, 881, 3, 112, 48, 0, 880, 879, 1, 0, 0, 0, 881, 882, 1, 0, 0, 0, 882, 880, 1, 0, 0, 0, 882, 883, 1, 0, 0, 0, 883, 115, 1, 0, 0, 0, 884, 885, 3, 114, 49, 0, 885, 886, 1, 0, 0, 0, 886, 887, 6, 50, 25, 0, 887, 117, 1, 0, 0, 0, 888, 889, 3, 16, 0, 0, 889, 890, 1, 0, 0, 0, 890, 891, 6, 51, 0, 0, 891, 119, 1, 0, 0, 0, 892, 893, 3, 18, 1, 0, 893, 894, 1, 0, 0, 0, 894, 895, 6, 52, 0, 0, 895, 121, 1, 0, 0, 0, 896, 897, 3, 20, 2, 0, 897, 898, 1, 0, 0, 0, 898, 899, 6, 53, 0, 0, 899, 123, 1, 0, 0, 0, 900, 901, 3, 174, 79, 0, 901, 902, 1, 0, 0, 0, 902, 903, 6, 54, 13, 0, 903, 904, 6, 54, 14, 0, 904, 905, 6, 54, 14, 0, 905, 125, 1, 0, 0, 0, 906, 907, 3, 206, 95, 0, 907, 908, 1, 0, 0, 0, 908, 909, 6, 55, 26, 0, 909, 127, 1, 0, 0, 0, 910, 911, 3, 214, 99, 0, 911, 912, 1, 0, 0, 0, 912, 913, 6, 56, 18, 0, 913, 129, 1, 0, 0, 0, 914, 915, 3, 218, 101, 0, 915, 916, 1, 0, 0, 0, 916, 917, 6, 57, 17, 0, 917, 131, 1, 0, 0, 0, 918, 919, 3, 248, 116, 0, 919, 920, 1, 0, 0, 0, 920, 921, 6, 58, 24, 0, 921, 133, 1, 0, 0, 0, 922, 923, 3, 446, 215, 0, 923, 924, 1, 0, 0, 0, 924, 925, 6, 59, 27, 0, 925, 135, 1, 0, 0, 0, 926, 927, 3, 298, 141, 0, 927, 928, 1, 0, 0, 0, 928, 929, 6, 60, 19, 0, 929, 137, 1, 0, 0, 0, 930, 931, 3, 242, 113, 0, 931, 932, 1, 0, 0, 0, 932, 933, 6, 61, 28, 0, 933, 139, 1, 0, 0, 0, 934, 935, 3, 282, 133, 0, 935, 936, 1, 0, 0, 0, 936, 937, 6, 62, 29, 0, 937, 141, 1, 0, 0, 0, 938, 939, 3, 278, 131, 0, 939, 940, 1, 0, 0, 0, 940, 941, 6, 63, 30, 0, 941, 143, 1, 0, 0, 0, 942, 943, 3, 284, 134, 0, 943, 944, 1, 0, 0, 0, 944, 945, 6, 64, 31, 0, 945, 145, 1, 0, 0, 0, 946, 947, 3, 16, 0, 0, 947, 948, 1, 0, 0, 0, 948, 949, 6, 65, 0, 0, 949, 147, 1, 0, 0, 0, 950, 951, 3, 18, 1, 0, 951, 952, 1, 0, 0, 0, 952, 953, 6, 66, 0, 0, 953, 149, 1, 0, 0, 0, 954, 955, 3, 20, 2, 0, 955, 956, 1, 0, 0, 0, 956, 957, 6, 67, 0, 0, 957, 151, 1, 0, 0, 0, 958, 959, 3, 288, 136, 0, 959, 960, 1, 0, 0, 0, 960, 961, 6, 68, 32, 0, 961, 962, 6, 68, 14, 0, 962, 153, 1, 0, 0, 0, 963, 964, 3, 212, 98, 0, 964, 965, 1, 0, 0, 0, 965, 966, 6, 69, 33, 0, 966, 155, 1, 0, 0, 0, 967, 973, 3, 186, 85, 0, 968, 973, 3, 176, 80, 0, 969, 973, 3, 218, 101, 0, 970, 973, 3, 178, 81, 0, 971, 973, 3, 192, 88, 0, 972, 967, 1, 0, 0, 0, 972, 968, 1, 0, 0, 0, 972, 969, 1, 0, 0, 0, 972, 970, 1, 0, 0, 0, 972, 971, 1, 0, 0, 0, 973, 974, 1, 0, 0, 0, 974, 972, 1, 0, 0, 0, 974, 975, 1, 0, 0, 0, 975, 157, 1, 0, 0, 0, 976, 977, 3, 16, 0, 0, 977, 978, 1, 0, 0, 0, 978, 979, 6, 71, 0, 0, 979, 159, 1, 0, 0, 0, 980, 981, 3, 18, 1, 0, 981, 982, 1, 0, 0, 0, 982, 983, 6, 72, 0, 0, 983, 161, 1, 0, 0, 0, 984, 985, 3, 20, 2, 0, 985, 986, 1, 0, 0, 0, 986, 987, 6, 73, 0, 0, 987, 163, 1, 0, 0, 0, 988, 989, 3, 286, 135, 0, 989, 990, 1, 0, 0, 0, 990, 991, 6, 74, 21, 0, 991, 992, 6, 74, 34, 0, 992, 165, 1, 0, 0, 0, 993, 994, 3, 174, 79, 0, 994, 995, 1, 0, 0, 0, 995, 996, 6, 75, 13, 0, 996, 997, 6, 75, 14, 0, 997, 167, 1, 0, 0, 0, 998, 999, 3, 20, 2, 0, 999, 1000, 1, 0, 0, 0, 1000, 1001, 6, 76, 0, 0, 1001, 169, 1, 0, 0, 0, 1002, 1003, 3, 16, 0, 0, 1003, 1004, 1, 0, 0, 0, 1004, 1005, 6, 77, 0, 0, 1005, 171, 1, 0, 0, 0, 1006, 1007, 3, 18, 1, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1009, 6, 78, 0, 0, 1009, 173, 1, 0, 0, 0, 1010, 1011, 5, 124, 0, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 79, 14, 0, 1013, 175, 1, 0, 0, 0, 1014, 1015, 7, 25, 0, 0, 1015, 177, 1, 0, 0, 0, 1016, 1017, 7, 26, 0, 0, 1017, 179, 1, 0, 0, 0, 1018, 1019, 5, 92, 0, 0, 1019, 1020, 7, 27, 0, 0, 1020, 181, 1, 0, 0, 0, 1021, 1022, 8, 28, 0, 0, 1022, 183, 1, 0, 0, 0, 1023, 1025, 7, 7, 0, 0, 1024, 1026, 7, 29, 0, 0, 1025, 1024, 1, 0, 0, 0, 1025, 1026, 1, 0, 0, 0, 1026, 1028, 1, 0, 0, 0, 1027, 1029, 3, 176, 80, 0, 1028, 1027, 1, 0, 0, 0, 1029, 1030, 1, 0, 0, 0, 1030, 1028, 1, 0, 0, 0, 1030, 1031, 1, 0, 0, 0, 1031, 185, 1, 0, 0, 0, 1032, 1033, 5, 64, 0, 0, 1033, 187, 1, 0, 0, 0, 1034, 1035, 5, 96, 0, 0, 1035, 189, 1, 0, 0, 0, 1036, 1040, 8, 30, 0, 0, 1037, 1038, 5, 96, 0, 0, 1038, 1040, 5, 96, 0, 0, 1039, 1036, 1, 0, 0, 0, 1039, 1037, 1, 0, 0, 0, 1040, 191, 1, 0, 0, 0, 1041, 1042, 5, 95, 0, 0, 1042, 193, 1, 0, 0, 0, 1043, 1047, 3, 178, 81, 0, 1044, 1047, 3, 176, 80, 0, 1045, 1047, 3, 192, 88, 0, 1046, 1043, 1, 0, 0, 0, 1046, 1044, 1, 0, 0, 0, 1046, 1045, 1, 0, 0, 0, 1047, 195, 1, 0, 0, 0, 1048, 1053, 5, 34, 0, 0, 1049, 1052, 3, 180, 82, 0, 1050, 1052, 3, 182, 83, 0, 1051, 1049, 1, 0, 0, 0, 1051, 1050, 1, 0, 0, 0, 1052, 1055, 1, 0, 0, 0, 1053, 1051, 1, 0, 0, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1056, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1056, 1078, 5, 34, 0, 0, 1057, 1058, 5, 34, 0, 0, 1058, 1059, 5, 34, 0, 0, 1059, 1060, 5, 34, 0, 0, 1060, 1064, 1, 0, 0, 0, 1061, 1063, 8, 0, 0, 0, 1062, 1061, 1, 0, 0, 0, 1063, 1066, 1, 0, 0, 0, 1064, 1065, 1, 0, 0, 0, 1064, 1062, 1, 0, 0, 0, 1065, 1067, 1, 0, 0, 0, 1066, 1064, 1, 0, 0, 0, 1067, 1068, 5, 34, 0, 0, 1068, 1069, 5, 34, 0, 0, 1069, 1070, 5, 34, 0, 0, 1070, 1072, 1, 0, 0, 0, 1071, 1073, 5, 34, 0, 0, 1072, 1071, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1075, 1, 0, 0, 0, 1074, 1076, 5, 34, 0, 0, 1075, 1074, 1, 0, 0, 0, 1075, 1076, 1, 0, 0, 0, 1076, 1078, 1, 0, 0, 0, 1077, 1048, 1, 0, 0, 0, 1077, 1057, 1, 0, 0, 0, 1078, 197, 1, 0, 0, 0, 1079, 1081, 3, 176, 80, 0, 1080, 1079, 1, 0, 0, 0, 1081, 1082, 1, 0, 0, 0, 1082, 1080, 1, 0, 0, 0, 1082, 1083, 1, 0, 0, 0, 1083, 199, 1, 0, 0, 0, 1084, 1086, 3, 176, 80, 0, 1085, 1084, 1, 0, 0, 0, 1086, 1087, 1, 0, 0, 0, 1087, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 1089, 1, 0, 0, 0, 1089, 1093, 3, 218, 101, 0, 1090, 1092, 3, 176, 80, 0, 1091, 1090, 1, 0, 0, 0, 1092, 1095, 1, 0, 0, 0, 1093, 1091, 1, 0, 0, 0, 1093, 1094, 1, 0, 0, 0, 1094, 1127, 1, 0, 0, 0, 1095, 1093, 1, 0, 0, 0, 1096, 1098, 3, 218, 101, 0, 1097, 1099, 3, 176, 80, 0, 1098, 1097, 1, 0, 0, 0, 1099, 1100, 1, 0, 0, 0, 1100, 1098, 1, 0, 0, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1127, 1, 0, 0, 0, 1102, 1104, 3, 176, 80, 0, 1103, 1102, 1, 0, 0, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1103, 1, 0, 0, 0, 1105, 1106, 1, 0, 0, 0, 1106, 1114, 1, 0, 0, 0, 1107, 1111, 3, 218, 101, 0, 1108, 1110, 3, 176, 80, 0, 1109, 1108, 1, 0, 0, 0, 1110, 1113, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1112, 1, 0, 0, 0, 1112, 1115, 1, 0, 0, 0, 1113, 1111, 1, 0, 0, 0, 1114, 1107, 1, 0, 0, 0, 1114, 1115, 1, 0, 0, 0, 1115, 1116, 1, 0, 0, 0, 1116, 1117, 3, 184, 84, 0, 1117, 1127, 1, 0, 0, 0, 1118, 1120, 3, 218, 101, 0, 1119, 1121, 3, 176, 80, 0, 1120, 1119, 1, 0, 0, 0, 1121, 1122, 1, 0, 0, 0, 1122, 1120, 1, 0, 0, 0, 1122, 1123, 1, 0, 0, 0, 1123, 1124, 1, 0, 0, 0, 1124, 1125, 3, 184, 84, 0, 1125, 1127, 1, 0, 0, 0, 1126, 1085, 1, 0, 0, 0, 1126, 1096, 1, 0, 0, 0, 1126, 1103, 1, 0, 0, 0, 1126, 1118, 1, 0, 0, 0, 1127, 201, 1, 0, 0, 0, 1128, 1129, 7, 4, 0, 0, 1129, 1130, 7, 5, 0, 0, 1130, 1131, 7, 16, 0, 0, 1131, 203, 1, 0, 0, 0, 1132, 1133, 7, 4, 0, 0, 1133, 1134, 7, 17, 0, 0, 1134, 1135, 7, 2, 0, 0, 1135, 205, 1, 0, 0, 0, 1136, 1137, 5, 61, 0, 0, 1137, 207, 1, 0, 0, 0, 1138, 1139, 7, 31, 0, 0, 1139, 1140, 7, 32, 0, 0, 1140, 209, 1, 0, 0, 0, 1141, 1142, 5, 58, 0, 0, 1142, 1143, 5, 58, 0, 0, 1143, 211, 1, 0, 0, 0, 1144, 1145, 5, 58, 0, 0, 1145, 213, 1, 0, 0, 0, 1146, 1147, 5, 44, 0, 0, 1147, 215, 1, 0, 0, 0, 1148, 1149, 7, 16, 0, 0, 1149, 1150, 7, 7, 0, 0, 1150, 1151, 7, 17, 0, 0, 1151, 1152, 7, 2, 0, 0, 1152, 217, 1, 0, 0, 0, 1153, 1154, 5, 46, 0, 0, 1154, 219, 1, 0, 0, 0, 1155, 1156, 7, 21, 0, 0, 1156, 1157, 7, 4, 0, 0, 1157, 1158, 7, 14, 0, 0, 1158, 1159, 7, 17, 0, 0, 1159, 1160, 7, 7, 0, 0, 1160, 221, 1, 0, 0, 0, 1161, 1162, 7, 21, 0, 0, 1162, 1163, 7, 10, 0, 0, 1163, 1164, 7, 12, 0, 0, 1164, 1165, 7, 17, 0, 0, 1165, 1166, 7, 11, 0, 0, 1166, 223, 1, 0, 0, 0, 1167, 1168, 7, 10, 0, 0, 1168, 1169, 7, 5, 0, 0, 1169, 225, 1, 0, 0, 0, 1170, 1171, 7, 10, 0, 0, 1171, 1172, 7, 17, 0, 0, 1172, 227, 1, 0, 0, 0, 1173, 1174, 7, 14, 0, 0, 1174, 1175, 7, 4, 0, 0, 1175, 1176, 7, 17, 0, 0, 1176, 1177, 7, 11, 0, 0, 1177, 229, 1, 0, 0, 0, 1178, 1179, 7, 14, 0, 0, 1179, 1180, 7, 10, 0, 0, 1180, 1181, 7, 19, 0, 0, 1181, 1182, 7, 7, 0, 0, 1182, 231, 1, 0, 0, 0, 1183, 1184, 7, 5, 0, 0, 1184, 1185, 7, 9, 0, 0, 1185, 1186, 7, 11, 0, 0, 1186, 233, 1, 0, 0, 0, 1187, 1188, 7, 5, 0, 0, 1188, 1189, 7, 22, 0, 0, 1189, 1190, 7, 14, 0, 0, 1190, 1191, 7, 14, 0, 0, 1191, 235, 1, 0, 0, 0, 1192, 1193, 7, 5, 0, 0, 1193, 1194, 7, 22, 0, 0, 1194, 1195, 7, 14, 0, 0, 1195, 1196, 7, 14, 0, 0, 1196, 1197, 7, 17, 0, 0, 1197, 237, 1, 0, 0, 0, 1198, 1199, 7, 9, 0, 0, 1199, 1200, 7, 5, 0, 0, 1200, 239, 1, 0, 0, 0, 1201, 1202, 7, 9, 0, 0, 1202, 1203, 7, 12, 0, 0, 1203, 241, 1, 0, 0, 0, 1204, 1205, 5, 63, 0, 0, 1205, 243, 1, 0, 0, 0, 1206, 1207, 7, 12, 0, 0, 1207, 1208, 7, 14, 0, 0, 1208, 1209, 7, 10, 0, 0, 1209, 1210, 7, 19, 0, 0, 1210, 1211, 7, 7, 0, 0, 1211, 245, 1, 0, 0, 0, 1212, 1213, 7, 11, 0, 0, 1213, 1214, 7, 12, 0, 0, 1214, 1215, 7, 22, 0, 0, 1215, 1216, 7, 7, 0, 0, 1216, 247, 1, 0, 0, 0, 1217, 1218, 7, 20, 0, 0, 1218, 1219, 7, 10, 0, 0, 1219, 1220, 7, 11, 0, 0, 1220, 1221, 7, 3, 0, 0, 1221, 249, 1, 0, 0, 0, 1222, 1223, 5, 61, 0, 0, 1223, 1224, 5, 61, 0, 0, 1224, 251, 1, 0, 0, 0, 1225, 1226, 5, 61, 0, 0, 1226, 1227, 5, 126, 0, 0, 1227, 253, 1, 0, 0, 0, 1228, 1229, 5, 33, 0, 0, 1229, 1230, 5, 61, 0, 0, 1230, 255, 1, 0, 0, 0, 1231, 1232, 5, 60, 0, 0, 1232, 257, 1, 0, 0, 0, 1233, 1234, 5, 60, 0, 0, 1234, 1235, 5, 61, 0, 0, 1235, 259, 1, 0, 0, 0, 1236, 1237, 5, 62, 0, 0, 1237, 261, 1, 0, 0, 0, 1238, 1239, 5, 62, 0, 0, 1239, 1240, 5, 61, 0, 0, 1240, 263, 1, 0, 0, 0, 1241, 1242, 5, 43, 0, 0, 1242, 265, 1, 0, 0, 0, 1243, 1244, 5, 45, 0, 0, 1244, 267, 1, 0, 0, 0, 1245, 1246, 5, 42, 0, 0, 1246, 269, 1, 0, 0, 0, 1247, 1248, 5, 47, 0, 0, 1248, 271, 1, 0, 0, 0, 1249, 1250, 5, 37, 0, 0, 1250, 273, 1, 0, 0, 0, 1251, 1252, 5, 123, 0, 0, 1252, 275, 1, 0, 0, 0, 1253, 1254, 5, 125, 0, 0, 1254, 277, 1, 0, 0, 0, 1255, 1256, 5, 63, 0, 0, 1256, 1257, 5, 63, 0, 0, 1257, 279, 1, 0, 0, 0, 1258, 1259, 3, 44, 14, 0, 1259, 1260, 1, 0, 0, 0, 1260, 1261, 6, 132, 35, 0, 1261, 281, 1, 0, 0, 0, 1262, 1265, 3, 242, 113, 0, 1263, 1266, 3, 178, 81, 0, 1264, 1266, 3, 192, 88, 0, 1265, 1263, 1, 0, 0, 0, 1265, 1264, 1, 0, 0, 0, 1266, 1270, 1, 0, 0, 0, 1267, 1269, 3, 194, 89, 0, 1268, 1267, 1, 0, 0, 0, 1269, 1272, 1, 0, 0, 0, 1270, 1268, 1, 0, 0, 0, 1270, 1271, 1, 0, 0, 0, 1271, 1280, 1, 0, 0, 0, 1272, 1270, 1, 0, 0, 0, 1273, 1275, 3, 242, 113, 0, 1274, 1276, 3, 176, 80, 0, 1275, 1274, 1, 0, 0, 0, 1276, 1277, 1, 0, 0, 0, 1277, 1275, 1, 0, 0, 0, 1277, 1278, 1, 0, 0, 0, 1278, 1280, 1, 0, 0, 0, 1279, 1262, 1, 0, 0, 0, 1279, 1273, 1, 0, 0, 0, 1280, 283, 1, 0, 0, 0, 1281, 1284, 3, 278, 131, 0, 1282, 1285, 3, 178, 81, 0, 1283, 1285, 3, 192, 88, 0, 1284, 1282, 1, 0, 0, 0, 1284, 1283, 1, 0, 0, 0, 1285, 1289, 1, 0, 0, 0, 1286, 1288, 3, 194, 89, 0, 1287, 1286, 1, 0, 0, 0, 1288, 1291, 1, 0, 0, 0, 1289, 1287, 1, 0, 0, 0, 1289, 1290, 1, 0, 0, 0, 1290, 1299, 1, 0, 0, 0, 1291, 1289, 1, 0, 0, 0, 1292, 1294, 3, 278, 131, 0, 1293, 1295, 3, 176, 80, 0, 1294, 1293, 1, 0, 0, 0, 1295, 1296, 1, 0, 0, 0, 1296, 1294, 1, 0, 0, 0, 1296, 1297, 1, 0, 0, 0, 1297, 1299, 1, 0, 0, 0, 1298, 1281, 1, 0, 0, 0, 1298, 1292, 1, 0, 0, 0, 1299, 285, 1, 0, 0, 0, 1300, 1301, 5, 91, 0, 0, 1301, 1302, 1, 0, 0, 0, 1302, 1303, 6, 135, 4, 0, 1303, 1304, 6, 135, 4, 0, 1304, 287, 1, 0, 0, 0, 1305, 1306, 5, 93, 0, 0, 1306, 1307, 1, 0, 0, 0, 1307, 1308, 6, 136, 14, 0, 1308, 1309, 6, 136, 14, 0, 1309, 289, 1, 0, 0, 0, 1310, 1311, 5, 40, 0, 0, 1311, 1312, 1, 0, 0, 0, 1312, 1313, 6, 137, 4, 0, 1313, 1314, 6, 137, 4, 0, 1314, 291, 1, 0, 0, 0, 1315, 1316, 5, 41, 0, 0, 1316, 1317, 1, 0, 0, 0, 1317, 1318, 6, 138, 14, 0, 1318, 1319, 6, 138, 14, 0, 1319, 293, 1, 0, 0, 0, 1320, 1324, 3, 178, 81, 0, 1321, 1323, 3, 194, 89, 0, 1322, 1321, 1, 0, 0, 0, 1323, 1326, 1, 0, 0, 0, 1324, 1322, 1, 0, 0, 0, 1324, 1325, 1, 0, 0, 0, 1325, 1337, 1, 0, 0, 0, 1326, 1324, 1, 0, 0, 0, 1327, 1330, 3, 192, 88, 0, 1328, 1330, 3, 186, 85, 0, 1329, 1327, 1, 0, 0, 0, 1329, 1328, 1, 0, 0, 0, 1330, 1332, 1, 0, 0, 0, 1331, 1333, 3, 194, 89, 0, 1332, 1331, 1, 0, 0, 0, 1333, 1334, 1, 0, 0, 0, 1334, 1332, 1, 0, 0, 0, 1334, 1335, 1, 0, 0, 0, 1335, 1337, 1, 0, 0, 0, 1336, 1320, 1, 0, 0, 0, 1336, 1329, 1, 0, 0, 0, 1337, 295, 1, 0, 0, 0, 1338, 1340, 3, 188, 86, 0, 1339, 1341, 3, 190, 87, 0, 1340, 1339, 1, 0, 0, 0, 1341, 1342, 1, 0, 0, 0, 1342, 1340, 1, 0, 0, 0, 1342, 1343, 1, 0, 0, 0, 1343, 1344, 1, 0, 0, 0, 1344, 1345, 3, 188, 86, 0, 1345, 297, 1, 0, 0, 0, 1346, 1347, 3, 296, 140, 0, 1347, 299, 1, 0, 0, 0, 1348, 1349, 3, 16, 0, 0, 1349, 1350, 1, 0, 0, 0, 1350, 1351, 6, 142, 0, 0, 1351, 301, 1, 0, 0, 0, 1352, 1353, 3, 18, 1, 0, 1353, 1354, 1, 0, 0, 0, 1354, 1355, 6, 143, 0, 0, 1355, 303, 1, 0, 0, 0, 1356, 1357, 3, 20, 2, 0, 1357, 1358, 1, 0, 0, 0, 1358, 1359, 6, 144, 0, 0, 1359, 305, 1, 0, 0, 0, 1360, 1361, 3, 174, 79, 0, 1361, 1362, 1, 0, 0, 0, 1362, 1363, 6, 145, 13, 0, 1363, 1364, 6, 145, 14, 0, 1364, 307, 1, 0, 0, 0, 1365, 1366, 3, 286, 135, 0, 1366, 1367, 1, 0, 0, 0, 1367, 1368, 6, 146, 21, 0, 1368, 309, 1, 0, 0, 0, 1369, 1370, 3, 288, 136, 0, 1370, 1371, 1, 0, 0, 0, 1371, 1372, 6, 147, 32, 0, 1372, 311, 1, 0, 0, 0, 1373, 1374, 3, 212, 98, 0, 1374, 1375, 1, 0, 0, 0, 1375, 1376, 6, 148, 33, 0, 1376, 313, 1, 0, 0, 0, 1377, 1378, 3, 210, 97, 0, 1378, 1379, 1, 0, 0, 0, 1379, 1380, 6, 149, 36, 0, 1380, 315, 1, 0, 0, 0, 1381, 1382, 3, 214, 99, 0, 1382, 1383, 1, 0, 0, 0, 1383, 1384, 6, 150, 18, 0, 1384, 317, 1, 0, 0, 0, 1385, 1386, 3, 206, 95, 0, 1386, 1387, 1, 0, 0, 0, 1387, 1388, 6, 151, 26, 0, 1388, 319, 1, 0, 0, 0, 1389, 1390, 7, 15, 0, 0, 1390, 1391, 7, 7, 0, 0, 1391, 1392, 7, 11, 0, 0, 1392, 1393, 7, 4, 0, 0, 1393, 1394, 7, 16, 0, 0, 1394, 1395, 7, 4, 0, 0, 1395, 1396, 7, 11, 0, 0, 1396, 1397, 7, 4, 0, 0, 1397, 321, 1, 0, 0, 0, 1398, 1402, 8, 33, 0, 0, 1399, 1400, 5, 47, 0, 0, 1400, 1402, 8, 34, 0, 0, 1401, 1398, 1, 0, 0, 0, 1401, 1399, 1, 0, 0, 0, 1402, 323, 1, 0, 0, 0, 1403, 1405, 3, 322, 153, 0, 1404, 1403, 1, 0, 0, 0, 1405, 1406, 1, 0, 0, 0, 1406, 1404, 1, 0, 0, 0, 1406, 1407, 1, 0, 0, 0, 1407, 325, 1, 0, 0, 0, 1408, 1409, 3, 324, 154, 0, 1409, 1410, 1, 0, 0, 0, 1410, 1411, 6, 155, 37, 0, 1411, 327, 1, 0, 0, 0, 1412, 1413, 3, 196, 90, 0, 1413, 1414, 1, 0, 0, 0, 1414, 1415, 6, 156, 38, 0, 1415, 329, 1, 0, 0, 0, 1416, 1417, 3, 16, 0, 0, 1417, 1418, 1, 0, 0, 0, 1418, 1419, 6, 157, 0, 0, 1419, 331, 1, 0, 0, 0, 1420, 1421, 3, 18, 1, 0, 1421, 1422, 1, 0, 0, 0, 1422, 1423, 6, 158, 0, 0, 1423, 333, 1, 0, 0, 0, 1424, 1425, 3, 20, 2, 0, 1425, 1426, 1, 0, 0, 0, 1426, 1427, 6, 159, 0, 0, 1427, 335, 1, 0, 0, 0, 1428, 1429, 3, 290, 137, 0, 1429, 1430, 1, 0, 0, 0, 1430, 1431, 6, 160, 39, 0, 1431, 1432, 6, 160, 34, 0, 1432, 337, 1, 0, 0, 0, 1433, 1434, 3, 174, 79, 0, 1434, 1435, 1, 0, 0, 0, 1435, 1436, 6, 161, 13, 0, 1436, 1437, 6, 161, 14, 0, 1437, 339, 1, 0, 0, 0, 1438, 1439, 3, 20, 2, 0, 1439, 1440, 1, 0, 0, 0, 1440, 1441, 6, 162, 0, 0, 1441, 341, 1, 0, 0, 0, 1442, 1443, 3, 16, 0, 0, 1443, 1444, 1, 0, 0, 0, 1444, 1445, 6, 163, 0, 0, 1445, 343, 1, 0, 0, 0, 1446, 1447, 3, 18, 1, 0, 1447, 1448, 1, 0, 0, 0, 1448, 1449, 6, 164, 0, 0, 1449, 345, 1, 0, 0, 0, 1450, 1451, 3, 174, 79, 0, 1451, 1452, 1, 0, 0, 0, 1452, 1453, 6, 165, 13, 0, 1453, 1454, 6, 165, 14, 0, 1454, 347, 1, 0, 0, 0, 1455, 1456, 7, 35, 0, 0, 1456, 1457, 7, 9, 0, 0, 1457, 1458, 7, 10, 0, 0, 1458, 1459, 7, 5, 0, 0, 1459, 349, 1, 0, 0, 0, 1460, 1461, 3, 470, 227, 0, 1461, 1462, 1, 0, 0, 0, 1462, 1463, 6, 167, 16, 0, 1463, 351, 1, 0, 0, 0, 1464, 1465, 3, 238, 111, 0, 1465, 1466, 1, 0, 0, 0, 1466, 1467, 6, 168, 15, 0, 1467, 1468, 6, 168, 14, 0, 1468, 1469, 6, 168, 4, 0, 1469, 353, 1, 0, 0, 0, 1470, 1471, 7, 22, 0, 0, 1471, 1472, 7, 17, 0, 0, 1472, 1473, 7, 10, 0, 0, 1473, 1474, 7, 5, 0, 0, 1474, 1475, 7, 6, 0, 0, 1475, 1476, 1, 0, 0, 0, 1476, 1477, 6, 169, 14, 0, 1477, 1478, 6, 169, 4, 0, 1478, 355, 1, 0, 0, 0, 1479, 1480, 3, 324, 154, 0, 1480, 1481, 1, 0, 0, 0, 1481, 1482, 6, 170, 37, 0, 1482, 357, 1, 0, 0, 0, 1483, 1484, 3, 196, 90, 0, 1484, 1485, 1, 0, 0, 0, 1485, 1486, 6, 171, 38, 0, 1486, 359, 1, 0, 0, 0, 1487, 1488, 3, 212, 98, 0, 1488, 1489, 1, 0, 0, 0, 1489, 1490, 6, 172, 33, 0, 1490, 361, 1, 0, 0, 0, 1491, 1492, 3, 294, 139, 0, 1492, 1493, 1, 0, 0, 0, 1493, 1494, 6, 173, 20, 0, 1494, 363, 1, 0, 0, 0, 1495, 1496, 3, 298, 141, 0, 1496, 1497, 1, 0, 0, 0, 1497, 1498, 6, 174, 19, 0, 1498, 365, 1, 0, 0, 0, 1499, 1500, 3, 16, 0, 0, 1500, 1501, 1, 0, 0, 0, 1501, 1502, 6, 175, 0, 0, 1502, 367, 1, 0, 0, 0, 1503, 1504, 3, 18, 1, 0, 1504, 1505, 1, 0, 0, 0, 1505, 1506, 6, 176, 0, 0, 1506, 369, 1, 0, 0, 0, 1507, 1508, 3, 20, 2, 0, 1508, 1509, 1, 0, 0, 0, 1509, 1510, 6, 177, 0, 0, 1510, 371, 1, 0, 0, 0, 1511, 1512, 3, 174, 79, 0, 1512, 1513, 1, 0, 0, 0, 1513, 1514, 6, 178, 13, 0, 1514, 1515, 6, 178, 14, 0, 1515, 373, 1, 0, 0, 0, 1516, 1517, 3, 212, 98, 0, 1517, 1518, 1, 0, 0, 0, 1518, 1519, 6, 179, 33, 0, 1519, 375, 1, 0, 0, 0, 1520, 1521, 3, 214, 99, 0, 1521, 1522, 1, 0, 0, 0, 1522, 1523, 6, 180, 18, 0, 1523, 377, 1, 0, 0, 0, 1524, 1525, 3, 218, 101, 0, 1525, 1526, 1, 0, 0, 0, 1526, 1527, 6, 181, 17, 0, 1527, 379, 1, 0, 0, 0, 1528, 1529, 3, 238, 111, 0, 1529, 1530, 1, 0, 0, 0, 1530, 1531, 6, 182, 15, 0, 1531, 1532, 6, 182, 40, 0, 1532, 381, 1, 0, 0, 0, 1533, 1534, 3, 324, 154, 0, 1534, 1535, 1, 0, 0, 0, 1535, 1536, 6, 183, 37, 0, 1536, 383, 1, 0, 0, 0, 1537, 1538, 3, 196, 90, 0, 1538, 1539, 1, 0, 0, 0, 1539, 1540, 6, 184, 38, 0, 1540, 385, 1, 0, 0, 0, 1541, 1542, 3, 16, 0, 0, 1542, 1543, 1, 0, 0, 0, 1543, 1544, 6, 185, 0, 0, 1544, 387, 1, 0, 0, 0, 1545, 1546, 3, 18, 1, 0, 1546, 1547, 1, 0, 0, 0, 1547, 1548, 6, 186, 0, 0, 1548, 389, 1, 0, 0, 0, 1549, 1550, 3, 20, 2, 0, 1550, 1551, 1, 0, 0, 0, 1551, 1552, 6, 187, 0, 0, 1552, 391, 1, 0, 0, 0, 1553, 1554, 3, 174, 79, 0, 1554, 1555, 1, 0, 0, 0, 1555, 1556, 6, 188, 13, 0, 1556, 1557, 6, 188, 14, 0, 1557, 1558, 6, 188, 14, 0, 1558, 393, 1, 0, 0, 0, 1559, 1560, 3, 214, 99, 0, 1560, 1561, 1, 0, 0, 0, 1561, 1562, 6, 189, 18, 0, 1562, 395, 1, 0, 0, 0, 1563, 1564, 3, 218, 101, 0, 1564, 1565, 1, 0, 0, 0, 1565, 1566, 6, 190, 17, 0, 1566, 397, 1, 0, 0, 0, 1567, 1568, 3, 446, 215, 0, 1568, 1569, 1, 0, 0, 0, 1569, 1570, 6, 191, 27, 0, 1570, 399, 1, 0, 0, 0, 1571, 1572, 3, 16, 0, 0, 1572, 1573, 1, 0, 0, 0, 1573, 1574, 6, 192, 0, 0, 1574, 401, 1, 0, 0, 0, 1575, 1576, 3, 18, 1, 0, 1576, 1577, 1, 0, 0, 0, 1577, 1578, 6, 193, 0, 0, 1578, 403, 1, 0, 0, 0, 1579, 1580, 3, 20, 2, 0, 1580, 1581, 1, 0, 0, 0, 1581, 1582, 6, 194, 0, 0, 1582, 405, 1, 0, 0, 0, 1583, 1584, 3, 174, 79, 0, 1584, 1585, 1, 0, 0, 0, 1585, 1586, 6, 195, 13, 0, 1586, 1587, 6, 195, 14, 0, 1587, 407, 1, 0, 0, 0, 1588, 1589, 3, 218, 101, 0, 1589, 1590, 1, 0, 0, 0, 1590, 1591, 6, 196, 17, 0, 1591, 409, 1, 0, 0, 0, 1592, 1593, 3, 242, 113, 0, 1593, 1594, 1, 0, 0, 0, 1594, 1595, 6, 197, 28, 0, 1595, 411, 1, 0, 0, 0, 1596, 1597, 3, 282, 133, 0, 1597, 1598, 1, 0, 0, 0, 1598, 1599, 6, 198, 29, 0, 1599, 413, 1, 0, 0, 0, 1600, 1601, 3, 278, 131, 0, 1601, 1602, 1, 0, 0, 0, 1602, 1603, 6, 199, 30, 0, 1603, 415, 1, 0, 0, 0, 1604, 1605, 3, 284, 134, 0, 1605, 1606, 1, 0, 0, 0, 1606, 1607, 6, 200, 31, 0, 1607, 417, 1, 0, 0, 0, 1608, 1609, 3, 298, 141, 0, 1609, 1610, 1, 0, 0, 0, 1610, 1611, 6, 201, 19, 0, 1611, 419, 1, 0, 0, 0, 1612, 1613, 3, 294, 139, 0, 1613, 1614, 1, 0, 0, 0, 1614, 1615, 6, 202, 20, 0, 1615, 421, 1, 0, 0, 0, 1616, 1617, 3, 16, 0, 0, 1617, 1618, 1, 0, 0, 0, 1618, 1619, 6, 203, 0, 0, 1619, 423, 1, 0, 0, 0, 1620, 1621, 3, 18, 1, 0, 1621, 1622, 1, 0, 0, 0, 1622, 1623, 6, 204, 0, 0, 1623, 425, 1, 0, 0, 0, 1624, 1625, 3, 20, 2, 0, 1625, 1626, 1, 0, 0, 0, 1626, 1627, 6, 205, 0, 0, 1627, 427, 1, 0, 0, 0, 1628, 1629, 3, 174, 79, 0, 1629, 1630, 1, 0, 0, 0, 1630, 1631, 6, 206, 13, 0, 1631, 1632, 6, 206, 14, 0, 1632, 429, 1, 0, 0, 0, 1633, 1634, 3, 218, 101, 0, 1634, 1635, 1, 0, 0, 0, 1635, 1636, 6, 207, 17, 0, 1636, 431, 1, 0, 0, 0, 1637, 1638, 3, 214, 99, 0, 1638, 1639, 1, 0, 0, 0, 1639, 1640, 6, 208, 18, 0, 1640, 433, 1, 0, 0, 0, 1641, 1642, 3, 242, 113, 0, 1642, 1643, 1, 0, 0, 0, 1643, 1644, 6, 209, 28, 0, 1644, 435, 1, 0, 0, 0, 1645, 1646, 3, 282, 133, 0, 1646, 1647, 1, 0, 0, 0, 1647, 1648, 6, 210, 29, 0, 1648, 437, 1, 0, 0, 0, 1649, 1650, 3, 278, 131, 0, 1650, 1651, 1, 0, 0, 0, 1651, 1652, 6, 211, 30, 0, 1652, 439, 1, 0, 0, 0, 1653, 1654, 3, 284, 134, 0, 1654, 1655, 1, 0, 0, 0, 1655, 1656, 6, 212, 31, 0, 1656, 441, 1, 0, 0, 0, 1657, 1662, 3, 178, 81, 0, 1658, 1662, 3, 176, 80, 0, 1659, 1662, 3, 192, 88, 0, 1660, 1662, 3, 268, 126, 0, 1661, 1657, 1, 0, 0, 0, 1661, 1658, 1, 0, 0, 0, 1661, 1659, 1, 0, 0, 0, 1661, 1660, 1, 0, 0, 0, 1662, 443, 1, 0, 0, 0, 1663, 1666, 3, 178, 81, 0, 1664, 1666, 3, 268, 126, 0, 1665, 1663, 1, 0, 0, 0, 1665, 1664, 1, 0, 0, 0, 1666, 1670, 1, 0, 0, 0, 1667, 1669, 3, 442, 213, 0, 1668, 1667, 1, 0, 0, 0, 1669, 1672, 1, 0, 0, 0, 1670, 1668, 1, 0, 0, 0, 1670, 1671, 1, 0, 0, 0, 1671, 1683, 1, 0, 0, 0, 1672, 1670, 1, 0, 0, 0, 1673, 1676, 3, 192, 88, 0, 1674, 1676, 3, 186, 85, 0, 1675, 1673, 1, 0, 0, 0, 1675, 1674, 1, 0, 0, 0, 1676, 1678, 1, 0, 0, 0, 1677, 1679, 3, 442, 213, 0, 1678, 1677, 1, 0, 0, 0, 1679, 1680, 1, 0, 0, 0, 1680, 1678, 1, 0, 0, 0, 1680, 1681, 1, 0, 0, 0, 1681, 1683, 1, 0, 0, 0, 1682, 1665, 1, 0, 0, 0, 1682, 1675, 1, 0, 0, 0, 1683, 445, 1, 0, 0, 0, 1684, 1687, 3, 444, 214, 0, 1685, 1687, 3, 296, 140, 0, 1686, 1684, 1, 0, 0, 0, 1686, 1685, 1, 0, 0, 0, 1687, 1688, 1, 0, 0, 0, 1688, 1686, 1, 0, 0, 0, 1688, 1689, 1, 0, 0, 0, 1689, 447, 1, 0, 0, 0, 1690, 1691, 3, 16, 0, 0, 1691, 1692, 1, 0, 0, 0, 1692, 1693, 6, 216, 0, 0, 1693, 449, 1, 0, 0, 0, 1694, 1695, 3, 18, 1, 0, 1695, 1696, 1, 0, 0, 0, 1696, 1697, 6, 217, 0, 0, 1697, 451, 1, 0, 0, 0, 1698, 1699, 3, 20, 2, 0, 1699, 1700, 1, 0, 0, 0, 1700, 1701, 6, 218, 0, 0, 1701, 453, 1, 0, 0, 0, 1702, 1703, 3, 174, 79, 0, 1703, 1704, 1, 0, 0, 0, 1704, 1705, 6, 219, 13, 0, 1705, 1706, 6, 219, 14, 0, 1706, 455, 1, 0, 0, 0, 1707, 1708, 3, 206, 95, 0, 1708, 1709, 1, 0, 0, 0, 1709, 1710, 6, 220, 26, 0, 1710, 457, 1, 0, 0, 0, 1711, 1712, 3, 214, 99, 0, 1712, 1713, 1, 0, 0, 0, 1713, 1714, 6, 221, 18, 0, 1714, 459, 1, 0, 0, 0, 1715, 1716, 3, 218, 101, 0, 1716, 1717, 1, 0, 0, 0, 1717, 1718, 6, 222, 17, 0, 1718, 461, 1, 0, 0, 0, 1719, 1720, 3, 242, 113, 0, 1720, 1721, 1, 0, 0, 0, 1721, 1722, 6, 223, 28, 0, 1722, 463, 1, 0, 0, 0, 1723, 1724, 3, 282, 133, 0, 1724, 1725, 1, 0, 0, 0, 1725, 1726, 6, 224, 29, 0, 1726, 465, 1, 0, 0, 0, 1727, 1728, 3, 278, 131, 0, 1728, 1729, 1, 0, 0, 0, 1729, 1730, 6, 225, 30, 0, 1730, 467, 1, 0, 0, 0, 1731, 1732, 3, 284, 134, 0, 1732, 1733, 1, 0, 0, 0, 1733, 1734, 6, 226, 31, 0, 1734, 469, 1, 0, 0, 0, 1735, 1736, 7, 4, 0, 0, 1736, 1737, 7, 17, 0, 0, 1737, 471, 1, 0, 0, 0, 1738, 1739, 3, 446, 215, 0, 1739, 1740, 1, 0, 0, 0, 1740, 1741, 6, 228, 27, 0, 1741, 473, 1, 0, 0, 0, 1742, 1743, 3, 16, 0, 0, 1743, 1744, 1, 0, 0, 0, 1744, 1745, 6, 229, 0, 0, 1745, 475, 1, 0, 0, 0, 1746, 1747, 3, 18, 1, 0, 1747, 1748, 1, 0, 0, 0, 1748, 1749, 6, 230, 0, 0, 1749, 477, 1, 0, 0, 0, 1750, 1751, 3, 20, 2, 0, 1751, 1752, 1, 0, 0, 0, 1752, 1753, 6, 231, 0, 0, 1753, 479, 1, 0, 0, 0, 1754, 1755, 3, 174, 79, 0, 1755, 1756, 1, 0, 0, 0, 1756, 1757, 6, 232, 13, 0, 1757, 1758, 6, 232, 14, 0, 1758, 481, 1, 0, 0, 0, 1759, 1760, 7, 10, 0, 0, 1760, 1761, 7, 5, 0, 0, 1761, 1762, 7, 21, 0, 0, 1762, 1763, 7, 9, 0, 0, 1763, 483, 1, 0, 0, 0, 1764, 1765, 3, 16, 0, 0, 1765, 1766, 1, 0, 0, 0, 1766, 1767, 6, 234, 0, 0, 1767, 485, 1, 0, 0, 0, 1768, 1769, 3, 18, 1, 0, 1769, 1770, 1, 0, 0, 0, 1770, 1771, 6, 235, 0, 0, 1771, 487, 1, 0, 0, 0, 1772, 1773, 3, 20, 2, 0, 1773, 1774, 1, 0, 0, 0, 1774, 1775, 6, 236, 0, 0, 1775, 489, 1, 0, 0, 0, 70, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 496, 500, 503, 512, 514, 525, 803, 873, 877, 882, 972, 974, 1025, 1030, 1039, 1046, 1051, 1053, 1064, 1072, 1075, 1077, 1082, 1087, 1093, 1100, 1105, 1111, 1114, 1122, 1126, 1265, 1270, 1277, 1279, 1284, 1289, 1296, 1298, 1324, 1329, 1334, 1336, 1342, 1401, 1406, 1661, 1665, 1670, 1675, 1680, 1682, 1686, 1688, 41, 0, 1, 0, 5, 1, 0, 5, 2, 0, 5, 5, 0, 5, 6, 0, 5, 7, 0, 5, 8, 0, 5, 9, 0, 5, 10, 0, 5, 12, 0, 5, 13, 0, 5, 14, 0, 5, 15, 0, 7, 52, 0, 4, 0, 0, 7, 74, 0, 7, 132, 0, 7, 64, 0, 7, 62, 0, 7, 102, 0, 7, 101, 0, 7, 97, 0, 5, 4, 0, 5, 3, 0, 7, 79, 0, 7, 38, 0, 7, 58, 0, 7, 128, 0, 7, 76, 0, 7, 95, 0, 7, 94, 0, 7, 96, 0, 7, 98, 0, 7, 61, 0, 5, 0, 0, 7, 15, 0, 7, 60, 0, 7, 107, 0, 7, 53, 0, 7, 99, 0, 5, 11, 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 302332bb649f5..9825b28800461 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 @@ -36,24 +36,23 @@ public class EsqlBaseLexer extends LexerConfig { ENRICH_FIELD_WS=44, SETTING=45, SETTING_LINE_COMMENT=46, SETTTING_MULTILINE_COMMENT=47, SETTING_WS=48, EXPLAIN_WS=49, EXPLAIN_LINE_COMMENT=50, EXPLAIN_MULTILINE_COMMENT=51, PIPE=52, QUOTED_STRING=53, INTEGER_LITERAL=54, DECIMAL_LITERAL=55, AND=56, - AS=57, ASC=58, ASSIGN=59, BY=60, CAST_OP=61, COLON=62, COMMA=63, DESC=64, - DOT=65, FALSE=66, FIRST=67, IN=68, IS=69, LAST=70, LIKE=71, NOT=72, NULL=73, - NULLS=74, ON=75, OR=76, PARAM=77, RLIKE=78, TRUE=79, WITH=80, EQ=81, CIEQ=82, - NEQ=83, LT=84, LTE=85, GT=86, GTE=87, PLUS=88, MINUS=89, ASTERISK=90, - SLASH=91, PERCENT=92, LEFT_BRACES=93, RIGHT_BRACES=94, DOUBLE_PARAMS=95, - NAMED_OR_POSITIONAL_PARAM=96, NAMED_OR_POSITIONAL_DOUBLE_PARAMS=97, OPENING_BRACKET=98, - CLOSING_BRACKET=99, LP=100, RP=101, UNQUOTED_IDENTIFIER=102, QUOTED_IDENTIFIER=103, - EXPR_LINE_COMMENT=104, EXPR_MULTILINE_COMMENT=105, EXPR_WS=106, METADATA=107, - UNQUOTED_SOURCE=108, FROM_LINE_COMMENT=109, FROM_MULTILINE_COMMENT=110, - FROM_WS=111, FORK_WS=112, FORK_LINE_COMMENT=113, FORK_MULTILINE_COMMENT=114, - JOIN=115, USING=116, JOIN_LINE_COMMENT=117, JOIN_MULTILINE_COMMENT=118, - JOIN_WS=119, LOOKUP_LINE_COMMENT=120, LOOKUP_MULTILINE_COMMENT=121, LOOKUP_WS=122, - LOOKUP_FIELD_LINE_COMMENT=123, LOOKUP_FIELD_MULTILINE_COMMENT=124, LOOKUP_FIELD_WS=125, - MVEXPAND_LINE_COMMENT=126, MVEXPAND_MULTILINE_COMMENT=127, MVEXPAND_WS=128, - ID_PATTERN=129, PROJECT_LINE_COMMENT=130, PROJECT_MULTILINE_COMMENT=131, - PROJECT_WS=132, RENAME_LINE_COMMENT=133, RENAME_MULTILINE_COMMENT=134, - RENAME_WS=135, INFO=136, SHOW_LINE_COMMENT=137, SHOW_MULTILINE_COMMENT=138, - SHOW_WS=139; + ASC=57, ASSIGN=58, BY=59, CAST_OP=60, COLON=61, COMMA=62, DESC=63, DOT=64, + FALSE=65, FIRST=66, IN=67, IS=68, LAST=69, LIKE=70, NOT=71, NULL=72, NULLS=73, + ON=74, OR=75, PARAM=76, RLIKE=77, TRUE=78, WITH=79, EQ=80, CIEQ=81, NEQ=82, + LT=83, LTE=84, GT=85, GTE=86, PLUS=87, MINUS=88, ASTERISK=89, SLASH=90, + PERCENT=91, LEFT_BRACES=92, RIGHT_BRACES=93, DOUBLE_PARAMS=94, NAMED_OR_POSITIONAL_PARAM=95, + NAMED_OR_POSITIONAL_DOUBLE_PARAMS=96, OPENING_BRACKET=97, CLOSING_BRACKET=98, + LP=99, RP=100, UNQUOTED_IDENTIFIER=101, QUOTED_IDENTIFIER=102, EXPR_LINE_COMMENT=103, + EXPR_MULTILINE_COMMENT=104, EXPR_WS=105, METADATA=106, UNQUOTED_SOURCE=107, + FROM_LINE_COMMENT=108, FROM_MULTILINE_COMMENT=109, FROM_WS=110, FORK_WS=111, + FORK_LINE_COMMENT=112, FORK_MULTILINE_COMMENT=113, JOIN=114, USING=115, + JOIN_LINE_COMMENT=116, JOIN_MULTILINE_COMMENT=117, JOIN_WS=118, LOOKUP_LINE_COMMENT=119, + LOOKUP_MULTILINE_COMMENT=120, LOOKUP_WS=121, LOOKUP_FIELD_LINE_COMMENT=122, + LOOKUP_FIELD_MULTILINE_COMMENT=123, LOOKUP_FIELD_WS=124, MVEXPAND_LINE_COMMENT=125, + MVEXPAND_MULTILINE_COMMENT=126, MVEXPAND_WS=127, ID_PATTERN=128, PROJECT_LINE_COMMENT=129, + PROJECT_MULTILINE_COMMENT=130, PROJECT_WS=131, AS=132, RENAME_LINE_COMMENT=133, + RENAME_MULTILINE_COMMENT=134, RENAME_WS=135, INFO=136, SHOW_LINE_COMMENT=137, + SHOW_MULTILINE_COMMENT=138, SHOW_WS=139; public static final int CHANGE_POINT_MODE=1, ENRICH_MODE=2, ENRICH_FIELD_MODE=3, SETTING_MODE=4, EXPLAIN_MODE=5, EXPRESSION_MODE=6, FROM_MODE=7, FORK_MODE=8, JOIN_MODE=9, @@ -93,12 +92,12 @@ private static String[] makeRuleNames() { "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", "DIGIT", "LETTER", "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "ASPERAND", "BACKQUOTE", "BACKQUOTE_BLOCK", "UNDERSCORE", "UNQUOTED_ID_BODY", "QUOTED_STRING", - "INTEGER_LITERAL", "DECIMAL_LITERAL", "AND", "AS", "ASC", "ASSIGN", "BY", - "CAST_OP", "COLON", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", - "LAST", "LIKE", "NOT", "NULL", "NULLS", "ON", "OR", "PARAM", "RLIKE", - "TRUE", "WITH", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", - "MINUS", "ASTERISK", "SLASH", "PERCENT", "LEFT_BRACES", "RIGHT_BRACES", - "DOUBLE_PARAMS", "NESTED_WHERE", "NAMED_OR_POSITIONAL_PARAM", "NAMED_OR_POSITIONAL_DOUBLE_PARAMS", + "INTEGER_LITERAL", "DECIMAL_LITERAL", "AND", "ASC", "ASSIGN", "BY", "CAST_OP", + "COLON", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", + "LIKE", "NOT", "NULL", "NULLS", "ON", "OR", "PARAM", "RLIKE", "TRUE", + "WITH", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", + "ASTERISK", "SLASH", "PERCENT", "LEFT_BRACES", "RIGHT_BRACES", "DOUBLE_PARAMS", + "NESTED_WHERE", "NAMED_OR_POSITIONAL_PARAM", "NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "OPENING_BRACKET", "CLOSING_BRACKET", "LP", "RP", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "FROM_PIPE", "FROM_OPENING_BRACKET", "FROM_CLOSING_BRACKET", @@ -122,7 +121,7 @@ private static String[] makeRuleNames() { "UNQUOTED_ID_BODY_WITH_PATTERN", "UNQUOTED_ID_PATTERN", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "RENAME_PIPE", "RENAME_ASSIGN", "RENAME_COMMA", "RENAME_DOT", "RENAME_PARAM", "RENAME_NAMED_OR_POSITIONAL_PARAM", - "RENAME_DOUBLE_PARAMS", "RENAME_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "RENAME_AS", + "RENAME_DOUBLE_PARAMS", "RENAME_NAMED_OR_POSITIONAL_DOUBLE_PARAMS", "AS", "RENAME_ID_PATTERN", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" @@ -138,14 +137,14 @@ private static String[] makeLiteralNames() { null, null, null, "'mv_expand'", "'drop'", "'keep'", null, null, "'rename'", "'show'", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "'|'", null, null, null, - "'and'", "'as'", "'asc'", "'='", "'by'", "'::'", "':'", "','", "'desc'", - "'.'", "'false'", "'first'", "'in'", "'is'", "'last'", "'like'", "'not'", - "'null'", "'nulls'", "'on'", "'or'", "'?'", "'rlike'", "'true'", "'with'", - "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", - "'/'", "'%'", "'{'", "'}'", "'??'", null, null, null, "']'", null, "')'", - null, null, null, null, null, "'metadata'", null, null, null, null, null, - null, null, "'join'", "'USING'", null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, + "'and'", "'asc'", "'='", "'by'", "'::'", "':'", "','", "'desc'", "'.'", + "'false'", "'first'", "'in'", "'is'", "'last'", "'like'", "'not'", "'null'", + "'nulls'", "'on'", "'or'", "'?'", "'rlike'", "'true'", "'with'", "'=='", + "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", + "'%'", "'{'", "'}'", "'??'", null, null, null, "']'", null, "')'", null, + null, null, null, null, "'metadata'", null, null, null, null, null, null, + null, "'join'", "'USING'", null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, "'as'", null, null, null, "'info'" }; } @@ -163,7 +162,7 @@ private static String[] makeSymbolicNames() { "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", "QUOTED_STRING", "INTEGER_LITERAL", - "DECIMAL_LITERAL", "AND", "AS", "ASC", "ASSIGN", "BY", "CAST_OP", "COLON", + "DECIMAL_LITERAL", "AND", "ASC", "ASSIGN", "BY", "CAST_OP", "COLON", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "NOT", "NULL", "NULLS", "ON", "OR", "PARAM", "RLIKE", "TRUE", "WITH", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", @@ -177,8 +176,8 @@ private static String[] makeSymbolicNames() { "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", - "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", "INFO", - "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" + "AS", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", + "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); @@ -347,7 +346,7 @@ private boolean DEV_RRF_sempred(RuleContext _localctx, int predIndex) { } public static final String _serializedATN = - "\u0004\u0000\u008b\u06f6\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff"+ + "\u0004\u0000\u008b\u06f0\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"+ @@ -417,192 +416,191 @@ private boolean DEV_RRF_sempred(RuleContext _localctx, int predIndex) { "\u00e3\u0002\u00e4\u0007\u00e4\u0002\u00e5\u0007\u00e5\u0002\u00e6\u0007"+ "\u00e6\u0002\u00e7\u0007\u00e7\u0002\u00e8\u0007\u00e8\u0002\u00e9\u0007"+ "\u00e9\u0002\u00ea\u0007\u00ea\u0002\u00eb\u0007\u00eb\u0002\u00ec\u0007"+ - "\u00ec\u0002\u00ed\u0007\u00ed\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+ - "\u0000\u0005\u0000\u01f1\b\u0000\n\u0000\f\u0000\u01f4\t\u0000\u0001\u0000"+ - "\u0003\u0000\u01f7\b\u0000\u0001\u0000\u0003\u0000\u01fa\b\u0000\u0001"+ - "\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ - "\u0001\u0005\u0001\u0203\b\u0001\n\u0001\f\u0001\u0206\t\u0001\u0001\u0001"+ - "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0004\u0002"+ - "\u020e\b\u0002\u000b\u0002\f\u0002\u020f\u0001\u0002\u0001\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\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"+ - "\u0005\u0001\u0005\u0001\u0005\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\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"+ - "\u0007\u0001\u0007\u0001\u0007\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"+ - "\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\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\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\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"+ - "\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\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001"+ - "\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0016\u0001"+ - "\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001"+ - "\u0016\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001"+ - "\u0017\u0001\u0017\u0001\u0017\u0001\u0018\u0001\u0018\u0001\u0018\u0001"+ - "\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001"+ - "\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001"+ - "\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001"+ - "\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001"+ - "\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001"+ - "\u001b\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001b\u0001"+ - "\u001b\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001"+ - "\u001c\u0001\u001c\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001"+ - "\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001"+ - "\u001d\u0001\u001d\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001"+ - "\u001e\u0001\u001e\u0001\u001e\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+ - "\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001"+ - " \u0001 \u0001 \u0001 \u0001 \u0001 \u0001 \u0001!\u0004!\u0324\b!\u000b"+ - "!\f!\u0325\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*\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\u00011\u00041\u036a"+ - "\b1\u000b1\f1\u036b\u00011\u00011\u00031\u0370\b1\u00011\u00041\u0373"+ - "\b1\u000b1\f1\u0374\u00012\u00012\u00012\u00012\u00013\u00013\u00013\u0001"+ - "3\u00014\u00014\u00014\u00014\u00015\u00015\u00015\u00015\u00016\u0001"+ - "6\u00016\u00016\u00016\u00016\u00017\u00017\u00017\u00017\u00018\u0001"+ - "8\u00018\u00018\u00019\u00019\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"+ - "?\u0001@\u0001@\u0001@\u0001@\u0001A\u0001A\u0001A\u0001A\u0001B\u0001"+ - "B\u0001B\u0001B\u0001C\u0001C\u0001C\u0001C\u0001D\u0001D\u0001D\u0001"+ - "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001"+ - "F\u0004F\u03cf\bF\u000bF\fF\u03d0\u0001G\u0001G\u0001G\u0001G\u0001H\u0001"+ - "H\u0001H\u0001H\u0001I\u0001I\u0001I\u0001I\u0001J\u0001J\u0001J\u0001"+ - "J\u0001J\u0001K\u0001K\u0001K\u0001K\u0001K\u0001L\u0001L\u0001L\u0001"+ - "L\u0001M\u0001M\u0001M\u0001M\u0001N\u0001N\u0001N\u0001N\u0001O\u0001"+ - "O\u0001O\u0001O\u0001P\u0001P\u0001Q\u0001Q\u0001R\u0001R\u0001R\u0001"+ - "S\u0001S\u0001T\u0001T\u0003T\u0404\bT\u0001T\u0004T\u0407\bT\u000bT\f"+ - "T\u0408\u0001U\u0001U\u0001V\u0001V\u0001W\u0001W\u0001W\u0003W\u0412"+ - "\bW\u0001X\u0001X\u0001Y\u0001Y\u0001Y\u0003Y\u0419\bY\u0001Z\u0001Z\u0001"+ - "Z\u0005Z\u041e\bZ\nZ\fZ\u0421\tZ\u0001Z\u0001Z\u0001Z\u0001Z\u0001Z\u0001"+ - "Z\u0005Z\u0429\bZ\nZ\fZ\u042c\tZ\u0001Z\u0001Z\u0001Z\u0001Z\u0001Z\u0003"+ - "Z\u0433\bZ\u0001Z\u0003Z\u0436\bZ\u0003Z\u0438\bZ\u0001[\u0004[\u043b"+ - "\b[\u000b[\f[\u043c\u0001\\\u0004\\\u0440\b\\\u000b\\\f\\\u0441\u0001"+ - "\\\u0001\\\u0005\\\u0446\b\\\n\\\f\\\u0449\t\\\u0001\\\u0001\\\u0004\\"+ - "\u044d\b\\\u000b\\\f\\\u044e\u0001\\\u0004\\\u0452\b\\\u000b\\\f\\\u0453"+ - "\u0001\\\u0001\\\u0005\\\u0458\b\\\n\\\f\\\u045b\t\\\u0003\\\u045d\b\\"+ - "\u0001\\\u0001\\\u0001\\\u0001\\\u0004\\\u0463\b\\\u000b\\\f\\\u0464\u0001"+ - "\\\u0001\\\u0003\\\u0469\b\\\u0001]\u0001]\u0001]\u0001]\u0001^\u0001"+ - "^\u0001^\u0001_\u0001_\u0001_\u0001_\u0001`\u0001`\u0001a\u0001a\u0001"+ - "a\u0001b\u0001b\u0001b\u0001c\u0001c\u0001d\u0001d\u0001e\u0001e\u0001"+ - "e\u0001e\u0001e\u0001f\u0001f\u0001g\u0001g\u0001g\u0001g\u0001g\u0001"+ - "g\u0001h\u0001h\u0001h\u0001h\u0001h\u0001h\u0001i\u0001i\u0001i\u0001"+ - "j\u0001j\u0001j\u0001k\u0001k\u0001k\u0001k\u0001k\u0001l\u0001l\u0001"+ - "l\u0001l\u0001l\u0001m\u0001m\u0001m\u0001m\u0001n\u0001n\u0001n\u0001"+ - "n\u0001n\u0001o\u0001o\u0001o\u0001o\u0001o\u0001o\u0001p\u0001p\u0001"+ - "p\u0001q\u0001q\u0001q\u0001r\u0001r\u0001s\u0001s\u0001s\u0001s\u0001"+ - "s\u0001s\u0001t\u0001t\u0001t\u0001t\u0001t\u0001u\u0001u\u0001u\u0001"+ - "u\u0001u\u0001v\u0001v\u0001v\u0001w\u0001w\u0001w\u0001x\u0001x\u0001"+ - "x\u0001y\u0001y\u0001z\u0001z\u0001z\u0001{\u0001{\u0001|\u0001|\u0001"+ - "|\u0001}\u0001}\u0001~\u0001~\u0001\u007f\u0001\u007f\u0001\u0080\u0001"+ - "\u0080\u0001\u0081\u0001\u0081\u0001\u0082\u0001\u0082\u0001\u0083\u0001"+ - "\u0083\u0001\u0084\u0001\u0084\u0001\u0084\u0001\u0085\u0001\u0085\u0001"+ - "\u0085\u0001\u0085\u0001\u0086\u0001\u0086\u0001\u0086\u0003\u0086\u04f7"+ - "\b\u0086\u0001\u0086\u0005\u0086\u04fa\b\u0086\n\u0086\f\u0086\u04fd\t"+ - "\u0086\u0001\u0086\u0001\u0086\u0004\u0086\u0501\b\u0086\u000b\u0086\f"+ - "\u0086\u0502\u0003\u0086\u0505\b\u0086\u0001\u0087\u0001\u0087\u0001\u0087"+ - "\u0003\u0087\u050a\b\u0087\u0001\u0087\u0005\u0087\u050d\b\u0087\n\u0087"+ - "\f\u0087\u0510\t\u0087\u0001\u0087\u0001\u0087\u0004\u0087\u0514\b\u0087"+ - "\u000b\u0087\f\u0087\u0515\u0003\u0087\u0518\b\u0087\u0001\u0088\u0001"+ + "\u00ec\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0005\u0000\u01ef"+ + "\b\u0000\n\u0000\f\u0000\u01f2\t\u0000\u0001\u0000\u0003\u0000\u01f5\b"+ + "\u0000\u0001\u0000\u0003\u0000\u01f8\b\u0000\u0001\u0000\u0001\u0000\u0001"+ + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0005\u0001\u0201"+ + "\b\u0001\n\u0001\f\u0001\u0204\t\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ + "\u0001\u0001\u0001\u0001\u0001\u0002\u0004\u0002\u020c\b\u0002\u000b\u0002"+ + "\f\u0002\u020d\u0001\u0002\u0001\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\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\u0005\u0001\u0005\u0001\u0005"+ + "\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\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\u0007\u0001\u0007\u0001\u0007"+ + "\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\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\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\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\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\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\u0015"+ + "\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015"+ + "\u0001\u0015\u0001\u0015\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016"+ + "\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017"+ + "\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017\u0001\u0017"+ + "\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0018"+ + "\u0001\u0018\u0001\u0018\u0001\u0018\u0001\u0019\u0001\u0019\u0001\u0019"+ + "\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019"+ + "\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u001a\u0001\u001a\u0001\u001a"+ + "\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a"+ + "\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001b\u0001\u001b\u0001\u001b"+ + "\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c"+ + "\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001d"+ + "\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d"+ + "\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001e"+ + "\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e"+ + "\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f\u0001\u001f"+ + "\u0001\u001f\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001 \u0001 \u0001"+ + " \u0001 \u0001 \u0001!\u0004!\u0322\b!\u000b!\f!\u0323\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*\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\u00011\u00041\u0368\b1\u000b1\f1\u0369\u00011\u0001"+ + "1\u00031\u036e\b1\u00011\u00041\u0371\b1\u000b1\f1\u0372\u00012\u0001"+ + "2\u00012\u00012\u00013\u00013\u00013\u00013\u00014\u00014\u00014\u0001"+ + "4\u00015\u00015\u00015\u00015\u00016\u00016\u00016\u00016\u00016\u0001"+ + "6\u00017\u00017\u00017\u00017\u00018\u00018\u00018\u00018\u00019\u0001"+ + "9\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?\u0001@\u0001@\u0001@\u0001"+ + "@\u0001A\u0001A\u0001A\u0001A\u0001B\u0001B\u0001B\u0001B\u0001C\u0001"+ + "C\u0001C\u0001C\u0001D\u0001D\u0001D\u0001D\u0001D\u0001E\u0001E\u0001"+ + "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001F\u0004F\u03cd\bF\u000bF\fF"+ + "\u03ce\u0001G\u0001G\u0001G\u0001G\u0001H\u0001H\u0001H\u0001H\u0001I"+ + "\u0001I\u0001I\u0001I\u0001J\u0001J\u0001J\u0001J\u0001J\u0001K\u0001"+ + "K\u0001K\u0001K\u0001K\u0001L\u0001L\u0001L\u0001L\u0001M\u0001M\u0001"+ + "M\u0001M\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001O\u0001O\u0001"+ + "P\u0001P\u0001Q\u0001Q\u0001R\u0001R\u0001R\u0001S\u0001S\u0001T\u0001"+ + "T\u0003T\u0402\bT\u0001T\u0004T\u0405\bT\u000bT\fT\u0406\u0001U\u0001"+ + "U\u0001V\u0001V\u0001W\u0001W\u0001W\u0003W\u0410\bW\u0001X\u0001X\u0001"+ + "Y\u0001Y\u0001Y\u0003Y\u0417\bY\u0001Z\u0001Z\u0001Z\u0005Z\u041c\bZ\n"+ + "Z\fZ\u041f\tZ\u0001Z\u0001Z\u0001Z\u0001Z\u0001Z\u0001Z\u0005Z\u0427\b"+ + "Z\nZ\fZ\u042a\tZ\u0001Z\u0001Z\u0001Z\u0001Z\u0001Z\u0003Z\u0431\bZ\u0001"+ + "Z\u0003Z\u0434\bZ\u0003Z\u0436\bZ\u0001[\u0004[\u0439\b[\u000b[\f[\u043a"+ + "\u0001\\\u0004\\\u043e\b\\\u000b\\\f\\\u043f\u0001\\\u0001\\\u0005\\\u0444"+ + "\b\\\n\\\f\\\u0447\t\\\u0001\\\u0001\\\u0004\\\u044b\b\\\u000b\\\f\\\u044c"+ + "\u0001\\\u0004\\\u0450\b\\\u000b\\\f\\\u0451\u0001\\\u0001\\\u0005\\\u0456"+ + "\b\\\n\\\f\\\u0459\t\\\u0003\\\u045b\b\\\u0001\\\u0001\\\u0001\\\u0001"+ + "\\\u0004\\\u0461\b\\\u000b\\\f\\\u0462\u0001\\\u0001\\\u0003\\\u0467\b"+ + "\\\u0001]\u0001]\u0001]\u0001]\u0001^\u0001^\u0001^\u0001^\u0001_\u0001"+ + "_\u0001`\u0001`\u0001`\u0001a\u0001a\u0001a\u0001b\u0001b\u0001c\u0001"+ + "c\u0001d\u0001d\u0001d\u0001d\u0001d\u0001e\u0001e\u0001f\u0001f\u0001"+ + "f\u0001f\u0001f\u0001f\u0001g\u0001g\u0001g\u0001g\u0001g\u0001g\u0001"+ + "h\u0001h\u0001h\u0001i\u0001i\u0001i\u0001j\u0001j\u0001j\u0001j\u0001"+ + "j\u0001k\u0001k\u0001k\u0001k\u0001k\u0001l\u0001l\u0001l\u0001l\u0001"+ + "m\u0001m\u0001m\u0001m\u0001m\u0001n\u0001n\u0001n\u0001n\u0001n\u0001"+ + "n\u0001o\u0001o\u0001o\u0001p\u0001p\u0001p\u0001q\u0001q\u0001r\u0001"+ + "r\u0001r\u0001r\u0001r\u0001r\u0001s\u0001s\u0001s\u0001s\u0001s\u0001"+ + "t\u0001t\u0001t\u0001t\u0001t\u0001u\u0001u\u0001u\u0001v\u0001v\u0001"+ + "v\u0001w\u0001w\u0001w\u0001x\u0001x\u0001y\u0001y\u0001y\u0001z\u0001"+ + "z\u0001{\u0001{\u0001{\u0001|\u0001|\u0001}\u0001}\u0001~\u0001~\u0001"+ + "\u007f\u0001\u007f\u0001\u0080\u0001\u0080\u0001\u0081\u0001\u0081\u0001"+ + "\u0082\u0001\u0082\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0084\u0001"+ + "\u0084\u0001\u0084\u0001\u0084\u0001\u0085\u0001\u0085\u0001\u0085\u0003"+ + "\u0085\u04f2\b\u0085\u0001\u0085\u0005\u0085\u04f5\b\u0085\n\u0085\f\u0085"+ + "\u04f8\t\u0085\u0001\u0085\u0001\u0085\u0004\u0085\u04fc\b\u0085\u000b"+ + "\u0085\f\u0085\u04fd\u0003\u0085\u0500\b\u0085\u0001\u0086\u0001\u0086"+ + "\u0001\u0086\u0003\u0086\u0505\b\u0086\u0001\u0086\u0005\u0086\u0508\b"+ + "\u0086\n\u0086\f\u0086\u050b\t\u0086\u0001\u0086\u0001\u0086\u0004\u0086"+ + "\u050f\b\u0086\u000b\u0086\f\u0086\u0510\u0003\u0086\u0513\b\u0086\u0001"+ + "\u0087\u0001\u0087\u0001\u0087\u0001\u0087\u0001\u0087\u0001\u0088\u0001"+ "\u0088\u0001\u0088\u0001\u0088\u0001\u0088\u0001\u0089\u0001\u0089\u0001"+ "\u0089\u0001\u0089\u0001\u0089\u0001\u008a\u0001\u008a\u0001\u008a\u0001"+ - "\u008a\u0001\u008a\u0001\u008b\u0001\u008b\u0001\u008b\u0001\u008b\u0001"+ - "\u008b\u0001\u008c\u0001\u008c\u0005\u008c\u0530\b\u008c\n\u008c\f\u008c"+ - "\u0533\t\u008c\u0001\u008c\u0001\u008c\u0003\u008c\u0537\b\u008c\u0001"+ - "\u008c\u0004\u008c\u053a\b\u008c\u000b\u008c\f\u008c\u053b\u0003\u008c"+ - "\u053e\b\u008c\u0001\u008d\u0001\u008d\u0004\u008d\u0542\b\u008d\u000b"+ - "\u008d\f\u008d\u0543\u0001\u008d\u0001\u008d\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\u0092\u0001\u0093\u0001"+ - "\u0093\u0001\u0093\u0001\u0093\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\u0099\u0001"+ - "\u0099\u0001\u0099\u0001\u0099\u0001\u0099\u0001\u0099\u0001\u0099\u0001"+ - "\u0099\u0001\u0099\u0001\u009a\u0001\u009a\u0001\u009a\u0003\u009a\u057f"+ - "\b\u009a\u0001\u009b\u0004\u009b\u0582\b\u009b\u000b\u009b\f\u009b\u0583"+ - "\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\u009f\u0001\u009f\u0001\u009f\u0001\u009f\u0001\u00a0\u0001\u00a0"+ + "\u008a\u0001\u008a\u0001\u008b\u0001\u008b\u0005\u008b\u052b\b\u008b\n"+ + "\u008b\f\u008b\u052e\t\u008b\u0001\u008b\u0001\u008b\u0003\u008b\u0532"+ + "\b\u008b\u0001\u008b\u0004\u008b\u0535\b\u008b\u000b\u008b\f\u008b\u0536"+ + "\u0003\u008b\u0539\b\u008b\u0001\u008c\u0001\u008c\u0004\u008c\u053d\b"+ + "\u008c\u000b\u008c\f\u008c\u053e\u0001\u008c\u0001\u008c\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\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"+ + "\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\u0098\u0001"+ + "\u0098\u0001\u0098\u0001\u0098\u0001\u0099\u0001\u0099\u0001\u0099\u0003"+ + "\u0099\u057a\b\u0099\u0001\u009a\u0004\u009a\u057d\b\u009a\u000b\u009a"+ + "\f\u009a\u057e\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\u009f"+ + "\u0001\u009f\u0001\u009f\u0001\u009f\u0001\u00a0\u0001\u00a0\u0001\u00a0"+ "\u0001\u00a0\u0001\u00a0\u0001\u00a1\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\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\u00a5\u0001\u00a5\u0001\u00a5\u0001\u00a5\u0001\u00a5"+ "\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a7"+ - "\u0001\u00a7\u0001\u00a7\u0001\u00a7\u0001\u00a7\u0001\u00a8\u0001\u00a8"+ - "\u0001\u00a8\u0001\u00a8\u0001\u00a9\u0001\u00a9\u0001\u00a9\u0001\u00a9"+ - "\u0001\u00a9\u0001\u00a9\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa"+ - "\u0001\u00aa\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\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\u00b3"+ - "\u0001\u00b4\u0001\u00b4\u0001\u00b4\u0001\u00b4\u0001\u00b5\u0001\u00b5"+ - "\u0001\u00b5\u0001\u00b5\u0001\u00b6\u0001\u00b6\u0001\u00b6\u0001\u00b6"+ - "\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\u00bc\u0001\u00bc\u0001\u00bc"+ - "\u0001\u00bc\u0001\u00bd\u0001\u00bd\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\u00c1\u0001\u00c1\u0001\u00c1\u0001\u00c1\u0001\u00c2"+ - "\u0001\u00c2\u0001\u00c2\u0001\u00c2\u0001\u00c3\u0001\u00c3\u0001\u00c3"+ - "\u0001\u00c3\u0001\u00c4\u0001\u00c4\u0001\u00c4\u0001\u00c4\u0001\u00c4"+ - "\u0001\u00c5\u0001\u00c5\u0001\u00c5\u0001\u00c5\u0001\u00c6\u0001\u00c6"+ - "\u0001\u00c6\u0001\u00c6\u0001\u00c7\u0001\u00c7\u0001\u00c7\u0001\u00c7"+ - "\u0001\u00c8\u0001\u00c8\u0001\u00c8\u0001\u00c8\u0001\u00c9\u0001\u00c9"+ - "\u0001\u00c9\u0001\u00c9\u0001\u00ca\u0001\u00ca\u0001\u00ca\u0001\u00ca"+ - "\u0001\u00cb\u0001\u00cb\u0001\u00cb\u0001\u00cb\u0001\u00cc\u0001\u00cc"+ - "\u0001\u00cc\u0001\u00cc\u0001\u00cd\u0001\u00cd\u0001\u00cd\u0001\u00cd"+ + "\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\u00a9\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\u00b0\u0001\u00b0\u0001\u00b0\u0001\u00b0\u0001\u00b1\u0001\u00b1"+ + "\u0001\u00b1\u0001\u00b1\u0001\u00b2\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\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\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\u00c1\u0001\u00c1\u0001\u00c1\u0001\u00c1\u0001\u00c2\u0001\u00c2"+ + "\u0001\u00c2\u0001\u00c2\u0001\u00c3\u0001\u00c3\u0001\u00c3\u0001\u00c3"+ + "\u0001\u00c3\u0001\u00c4\u0001\u00c4\u0001\u00c4\u0001\u00c4\u0001\u00c5"+ + "\u0001\u00c5\u0001\u00c5\u0001\u00c5\u0001\u00c6\u0001\u00c6\u0001\u00c6"+ + "\u0001\u00c6\u0001\u00c7\u0001\u00c7\u0001\u00c7\u0001\u00c7\u0001\u00c8"+ + "\u0001\u00c8\u0001\u00c8\u0001\u00c8\u0001\u00c9\u0001\u00c9\u0001\u00c9"+ + "\u0001\u00c9\u0001\u00ca\u0001\u00ca\u0001\u00ca\u0001\u00ca\u0001\u00cb"+ + "\u0001\u00cb\u0001\u00cb\u0001\u00cb\u0001\u00cc\u0001\u00cc\u0001\u00cc"+ + "\u0001\u00cc\u0001\u00cd\u0001\u00cd\u0001\u00cd\u0001\u00cd\u0001\u00ce"+ "\u0001\u00ce\u0001\u00ce\u0001\u00ce\u0001\u00ce\u0001\u00cf\u0001\u00cf"+ - "\u0001\u00cf\u0001\u00cf\u0001\u00cf\u0001\u00d0\u0001\u00d0\u0001\u00d0"+ - "\u0001\u00d0\u0001\u00d1\u0001\u00d1\u0001\u00d1\u0001\u00d1\u0001\u00d2"+ - "\u0001\u00d2\u0001\u00d2\u0001\u00d2\u0001\u00d3\u0001\u00d3\u0001\u00d3"+ - "\u0001\u00d3\u0001\u00d4\u0001\u00d4\u0001\u00d4\u0001\u00d4\u0001\u00d5"+ - "\u0001\u00d5\u0001\u00d5\u0001\u00d5\u0001\u00d6\u0001\u00d6\u0001\u00d6"+ - "\u0001\u00d6\u0003\u00d6\u0683\b\u00d6\u0001\u00d7\u0001\u00d7\u0003\u00d7"+ - "\u0687\b\u00d7\u0001\u00d7\u0005\u00d7\u068a\b\u00d7\n\u00d7\f\u00d7\u068d"+ - "\t\u00d7\u0001\u00d7\u0001\u00d7\u0003\u00d7\u0691\b\u00d7\u0001\u00d7"+ - "\u0004\u00d7\u0694\b\u00d7\u000b\u00d7\f\u00d7\u0695\u0003\u00d7\u0698"+ - "\b\u00d7\u0001\u00d8\u0001\u00d8\u0004\u00d8\u069c\b\u00d8\u000b\u00d8"+ - "\f\u00d8\u069d\u0001\u00d9\u0001\u00d9\u0001\u00d9\u0001\u00d9\u0001\u00da"+ - "\u0001\u00da\u0001\u00da\u0001\u00da\u0001\u00db\u0001\u00db\u0001\u00db"+ - "\u0001\u00db\u0001\u00dc\u0001\u00dc\u0001\u00dc\u0001\u00dc\u0001\u00dc"+ - "\u0001\u00dd\u0001\u00dd\u0001\u00dd\u0001\u00dd\u0001\u00de\u0001\u00de"+ - "\u0001\u00de\u0001\u00de\u0001\u00df\u0001\u00df\u0001\u00df\u0001\u00df"+ - "\u0001\u00e0\u0001\u00e0\u0001\u00e0\u0001\u00e0\u0001\u00e1\u0001\u00e1"+ - "\u0001\u00e1\u0001\u00e1\u0001\u00e2\u0001\u00e2\u0001\u00e2\u0001\u00e2"+ - "\u0001\u00e3\u0001\u00e3\u0001\u00e3\u0001\u00e3\u0001\u00e4\u0001\u00e4"+ - "\u0001\u00e4\u0001\u00e4\u0001\u00e5\u0001\u00e5\u0001\u00e5\u0001\u00e5"+ - "\u0001\u00e6\u0001\u00e6\u0001\u00e6\u0001\u00e6\u0001\u00e7\u0001\u00e7"+ - "\u0001\u00e7\u0001\u00e7\u0001\u00e8\u0001\u00e8\u0001\u00e8\u0001\u00e8"+ - "\u0001\u00e9\u0001\u00e9\u0001\u00e9\u0001\u00e9\u0001\u00e9\u0001\u00ea"+ - "\u0001\u00ea\u0001\u00ea\u0001\u00ea\u0001\u00ea\u0001\u00eb\u0001\u00eb"+ - "\u0001\u00eb\u0001\u00eb\u0001\u00ec\u0001\u00ec\u0001\u00ec\u0001\u00ec"+ - "\u0001\u00ed\u0001\u00ed\u0001\u00ed\u0001\u00ed\u0002\u0204\u042a\u0000"+ - "\u00ee\u0010\u0001\u0012\u0002\u0014\u0003\u0016\u0004\u0018\u0005\u001a"+ + "\u0001\u00cf\u0001\u00cf\u0001\u00d0\u0001\u00d0\u0001\u00d0\u0001\u00d0"+ + "\u0001\u00d1\u0001\u00d1\u0001\u00d1\u0001\u00d1\u0001\u00d2\u0001\u00d2"+ + "\u0001\u00d2\u0001\u00d2\u0001\u00d3\u0001\u00d3\u0001\u00d3\u0001\u00d3"+ + "\u0001\u00d4\u0001\u00d4\u0001\u00d4\u0001\u00d4\u0001\u00d5\u0001\u00d5"+ + "\u0001\u00d5\u0001\u00d5\u0003\u00d5\u067e\b\u00d5\u0001\u00d6\u0001\u00d6"+ + "\u0003\u00d6\u0682\b\u00d6\u0001\u00d6\u0005\u00d6\u0685\b\u00d6\n\u00d6"+ + "\f\u00d6\u0688\t\u00d6\u0001\u00d6\u0001\u00d6\u0003\u00d6\u068c\b\u00d6"+ + "\u0001\u00d6\u0004\u00d6\u068f\b\u00d6\u000b\u00d6\f\u00d6\u0690\u0003"+ + "\u00d6\u0693\b\u00d6\u0001\u00d7\u0001\u00d7\u0004\u00d7\u0697\b\u00d7"+ + "\u000b\u00d7\f\u00d7\u0698\u0001\u00d8\u0001\u00d8\u0001\u00d8\u0001\u00d8"+ + "\u0001\u00d9\u0001\u00d9\u0001\u00d9\u0001\u00d9\u0001\u00da\u0001\u00da"+ + "\u0001\u00da\u0001\u00da\u0001\u00db\u0001\u00db\u0001\u00db\u0001\u00db"+ + "\u0001\u00db\u0001\u00dc\u0001\u00dc\u0001\u00dc\u0001\u00dc\u0001\u00dd"+ + "\u0001\u00dd\u0001\u00dd\u0001\u00dd\u0001\u00de\u0001\u00de\u0001\u00de"+ + "\u0001\u00de\u0001\u00df\u0001\u00df\u0001\u00df\u0001\u00df\u0001\u00e0"+ + "\u0001\u00e0\u0001\u00e0\u0001\u00e0\u0001\u00e1\u0001\u00e1\u0001\u00e1"+ + "\u0001\u00e1\u0001\u00e2\u0001\u00e2\u0001\u00e2\u0001\u00e2\u0001\u00e3"+ + "\u0001\u00e3\u0001\u00e3\u0001\u00e4\u0001\u00e4\u0001\u00e4\u0001\u00e4"+ + "\u0001\u00e5\u0001\u00e5\u0001\u00e5\u0001\u00e5\u0001\u00e6\u0001\u00e6"+ + "\u0001\u00e6\u0001\u00e6\u0001\u00e7\u0001\u00e7\u0001\u00e7\u0001\u00e7"+ + "\u0001\u00e8\u0001\u00e8\u0001\u00e8\u0001\u00e8\u0001\u00e8\u0001\u00e9"+ + "\u0001\u00e9\u0001\u00e9\u0001\u00e9\u0001\u00e9\u0001\u00ea\u0001\u00ea"+ + "\u0001\u00ea\u0001\u00ea\u0001\u00eb\u0001\u00eb\u0001\u00eb\u0001\u00eb"+ + "\u0001\u00ec\u0001\u00ec\u0001\u00ec\u0001\u00ec\u0002\u0202\u0428\u0000"+ + "\u00ed\u0010\u0001\u0012\u0002\u0014\u0003\u0016\u0004\u0018\u0005\u001a"+ "\u0006\u001c\u0007\u001e\b \t\"\n$\u000b&\f(\r*\u000e,\u000f.\u00100\u0011"+ "2\u00124\u00136\u00148\u0015:\u0016<\u0017>\u0018@\u0019B\u001aD\u001b"+ "F\u001cH\u001dJ\u001eL\u001fN P!R\"T\u0000V\u0000X\u0000Z\u0000\\\u0000"+ @@ -616,916 +614,912 @@ private boolean DEV_RRF_sempred(RuleContext _localctx, int predIndex) { "=\u00d6>\u00d8?\u00da@\u00dcA\u00deB\u00e0C\u00e2D\u00e4E\u00e6F\u00e8"+ "G\u00eaH\u00ecI\u00eeJ\u00f0K\u00f2L\u00f4M\u00f6N\u00f8O\u00faP\u00fc"+ "Q\u00feR\u0100S\u0102T\u0104U\u0106V\u0108W\u010aX\u010cY\u010eZ\u0110"+ - "[\u0112\\\u0114]\u0116^\u0118_\u011a\u0000\u011c`\u011ea\u0120b\u0122"+ - "c\u0124d\u0126e\u0128f\u012a\u0000\u012cg\u012eh\u0130i\u0132j\u0134\u0000"+ - "\u0136\u0000\u0138\u0000\u013a\u0000\u013c\u0000\u013e\u0000\u0140\u0000"+ - "\u0142k\u0144\u0000\u0146l\u0148\u0000\u014a\u0000\u014cm\u014en\u0150"+ - "o\u0152\u0000\u0154\u0000\u0156p\u0158q\u015ar\u015c\u0000\u015es\u0160"+ - "\u0000\u0162\u0000\u0164t\u0166\u0000\u0168\u0000\u016a\u0000\u016c\u0000"+ - "\u016e\u0000\u0170u\u0172v\u0174w\u0176\u0000\u0178\u0000\u017a\u0000"+ - "\u017c\u0000\u017e\u0000\u0180\u0000\u0182\u0000\u0184x\u0186y\u0188z"+ - "\u018a\u0000\u018c\u0000\u018e\u0000\u0190\u0000\u0192{\u0194|\u0196}"+ - "\u0198\u0000\u019a\u0000\u019c\u0000\u019e\u0000\u01a0\u0000\u01a2\u0000"+ - "\u01a4\u0000\u01a6\u0000\u01a8~\u01aa\u007f\u01ac\u0080\u01ae\u0000\u01b0"+ + "[\u0112\\\u0114]\u0116^\u0118\u0000\u011a_\u011c`\u011ea\u0120b\u0122"+ + "c\u0124d\u0126e\u0128\u0000\u012af\u012cg\u012eh\u0130i\u0132\u0000\u0134"+ + "\u0000\u0136\u0000\u0138\u0000\u013a\u0000\u013c\u0000\u013e\u0000\u0140"+ + "j\u0142\u0000\u0144k\u0146\u0000\u0148\u0000\u014al\u014cm\u014en\u0150"+ + "\u0000\u0152\u0000\u0154o\u0156p\u0158q\u015a\u0000\u015cr\u015e\u0000"+ + "\u0160\u0000\u0162s\u0164\u0000\u0166\u0000\u0168\u0000\u016a\u0000\u016c"+ + "\u0000\u016et\u0170u\u0172v\u0174\u0000\u0176\u0000\u0178\u0000\u017a"+ + "\u0000\u017c\u0000\u017e\u0000\u0180\u0000\u0182w\u0184x\u0186y\u0188"+ + "\u0000\u018a\u0000\u018c\u0000\u018e\u0000\u0190z\u0192{\u0194|\u0196"+ + "\u0000\u0198\u0000\u019a\u0000\u019c\u0000\u019e\u0000\u01a0\u0000\u01a2"+ + "\u0000\u01a4\u0000\u01a6}\u01a8~\u01aa\u007f\u01ac\u0000\u01ae\u0000\u01b0"+ "\u0000\u01b2\u0000\u01b4\u0000\u01b6\u0000\u01b8\u0000\u01ba\u0000\u01bc"+ - "\u0000\u01be\u0000\u01c0\u0081\u01c2\u0082\u01c4\u0083\u01c6\u0084\u01c8"+ + "\u0000\u01be\u0080\u01c0\u0081\u01c2\u0082\u01c4\u0083\u01c6\u0000\u01c8"+ "\u0000\u01ca\u0000\u01cc\u0000\u01ce\u0000\u01d0\u0000\u01d2\u0000\u01d4"+ - "\u0000\u01d6\u0000\u01d8\u0000\u01da\u0000\u01dc\u0085\u01de\u0086\u01e0"+ - "\u0087\u01e2\u0000\u01e4\u0088\u01e6\u0089\u01e8\u008a\u01ea\u008b\u0010"+ - "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e"+ - "\u000f$\u0002\u0000\n\n\r\r\u0003\u0000\t\n\r\r \u0002\u0000CCcc\u0002"+ - "\u0000HHhh\u0002\u0000AAaa\u0002\u0000NNnn\u0002\u0000GGgg\u0002\u0000"+ - "EEee\u0002\u0000PPpp\u0002\u0000OOoo\u0002\u0000IIii\u0002\u0000TTtt\u0002"+ - "\u0000RRrr\u0002\u0000XXxx\u0002\u0000LLll\u0002\u0000MMmm\u0002\u0000"+ - "DDdd\u0002\u0000SSss\u0002\u0000VVvv\u0002\u0000KKkk\u0002\u0000WWww\u0002"+ - "\u0000FFff\u0002\u0000UUuu\u0006\u0000\t\n\r\r //[[]]\u000b\u0000\t\n"+ - "\r\r \"#,,//::<<>?\\\\||\u0001\u000009\u0002\u0000AZaz\b\u0000\"\"NN"+ - "RRTT\\\\nnrrtt\u0004\u0000\n\n\r\r\"\"\\\\\u0002\u0000++--\u0001\u0000"+ - "``\u0002\u0000BBbb\u0002\u0000YYyy\u000b\u0000\t\n\r\r \"\",,//::==["+ - "[]]||\u0002\u0000**//\u0002\u0000JJjj\u0715\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\u00002\u0001\u0000\u0000\u0000\u0000"+ - "4\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\u0000"+ - "B\u0001\u0000\u0000\u0000\u0000D\u0001\u0000\u0000\u0000\u0000F\u0001"+ - "\u0000\u0000\u0000\u0000H\u0001\u0000\u0000\u0000\u0000J\u0001\u0000\u0000"+ - "\u0000\u0000L\u0001\u0000\u0000\u0000\u0000N\u0001\u0000\u0000\u0000\u0000"+ - "P\u0001\u0000\u0000\u0000\u0000R\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`\u0001\u0000\u0000\u0000\u0001b"+ - "\u0001\u0000\u0000\u0000\u0001d\u0001\u0000\u0000\u0000\u0001f\u0001\u0000"+ - "\u0000\u0000\u0002h\u0001\u0000\u0000\u0000\u0002j\u0001\u0000\u0000\u0000"+ - "\u0002l\u0001\u0000\u0000\u0000\u0002n\u0001\u0000\u0000\u0000\u0002r"+ - "\u0001\u0000\u0000\u0000\u0002t\u0001\u0000\u0000\u0000\u0002v\u0001\u0000"+ - "\u0000\u0000\u0002x\u0001\u0000\u0000\u0000\u0002z\u0001\u0000\u0000\u0000"+ - "\u0003|\u0001\u0000\u0000\u0000\u0003~\u0001\u0000\u0000\u0000\u0003\u0080"+ - "\u0001\u0000\u0000\u0000\u0003\u0082\u0001\u0000\u0000\u0000\u0003\u0084"+ - "\u0001\u0000\u0000\u0000\u0003\u0086\u0001\u0000\u0000\u0000\u0003\u0088"+ - "\u0001\u0000\u0000\u0000\u0003\u008a\u0001\u0000\u0000\u0000\u0003\u008c"+ - "\u0001\u0000\u0000\u0000\u0003\u008e\u0001\u0000\u0000\u0000\u0003\u0090"+ - "\u0001\u0000\u0000\u0000\u0003\u0092\u0001\u0000\u0000\u0000\u0003\u0094"+ - "\u0001\u0000\u0000\u0000\u0003\u0096\u0001\u0000\u0000\u0000\u0004\u0098"+ - "\u0001\u0000\u0000\u0000\u0004\u009a\u0001\u0000\u0000\u0000\u0004\u009c"+ - "\u0001\u0000\u0000\u0000\u0004\u009e\u0001\u0000\u0000\u0000\u0004\u00a0"+ - "\u0001\u0000\u0000\u0000\u0004\u00a2\u0001\u0000\u0000\u0000\u0005\u00a4"+ - "\u0001\u0000\u0000\u0000\u0005\u00a6\u0001\u0000\u0000\u0000\u0005\u00a8"+ - "\u0001\u0000\u0000\u0000\u0005\u00aa\u0001\u0000\u0000\u0000\u0005\u00ac"+ - "\u0001\u0000\u0000\u0000\u0006\u00ae\u0001\u0000\u0000\u0000\u0006\u00c4"+ - "\u0001\u0000\u0000\u0000\u0006\u00c6\u0001\u0000\u0000\u0000\u0006\u00c8"+ - "\u0001\u0000\u0000\u0000\u0006\u00ca\u0001\u0000\u0000\u0000\u0006\u00cc"+ - "\u0001\u0000\u0000\u0000\u0006\u00ce\u0001\u0000\u0000\u0000\u0006\u00d0"+ - "\u0001\u0000\u0000\u0000\u0006\u00d2\u0001\u0000\u0000\u0000\u0006\u00d4"+ - "\u0001\u0000\u0000\u0000\u0006\u00d6\u0001\u0000\u0000\u0000\u0006\u00d8"+ - "\u0001\u0000\u0000\u0000\u0006\u00da\u0001\u0000\u0000\u0000\u0006\u00dc"+ - "\u0001\u0000\u0000\u0000\u0006\u00de\u0001\u0000\u0000\u0000\u0006\u00e0"+ - "\u0001\u0000\u0000\u0000\u0006\u00e2\u0001\u0000\u0000\u0000\u0006\u00e4"+ - "\u0001\u0000\u0000\u0000\u0006\u00e6\u0001\u0000\u0000\u0000\u0006\u00e8"+ - "\u0001\u0000\u0000\u0000\u0006\u00ea\u0001\u0000\u0000\u0000\u0006\u00ec"+ - "\u0001\u0000\u0000\u0000\u0006\u00ee\u0001\u0000\u0000\u0000\u0006\u00f0"+ - "\u0001\u0000\u0000\u0000\u0006\u00f2\u0001\u0000\u0000\u0000\u0006\u00f4"+ - "\u0001\u0000\u0000\u0000\u0006\u00f6\u0001\u0000\u0000\u0000\u0006\u00f8"+ - "\u0001\u0000\u0000\u0000\u0006\u00fa\u0001\u0000\u0000\u0000\u0006\u00fc"+ - "\u0001\u0000\u0000\u0000\u0006\u00fe\u0001\u0000\u0000\u0000\u0006\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\u010a\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\u0006\u0116\u0001\u0000\u0000\u0000\u0006\u0118"+ - "\u0001\u0000\u0000\u0000\u0006\u011a\u0001\u0000\u0000\u0000\u0006\u011c"+ - "\u0001\u0000\u0000\u0000\u0006\u011e\u0001\u0000\u0000\u0000\u0006\u0120"+ - "\u0001\u0000\u0000\u0000\u0006\u0122\u0001\u0000\u0000\u0000\u0006\u0124"+ - "\u0001\u0000\u0000\u0000\u0006\u0126\u0001\u0000\u0000\u0000\u0006\u0128"+ - "\u0001\u0000\u0000\u0000\u0006\u012c\u0001\u0000\u0000\u0000\u0006\u012e"+ - "\u0001\u0000\u0000\u0000\u0006\u0130\u0001\u0000\u0000\u0000\u0006\u0132"+ - "\u0001\u0000\u0000\u0000\u0007\u0134\u0001\u0000\u0000\u0000\u0007\u0136"+ - "\u0001\u0000\u0000\u0000\u0007\u0138\u0001\u0000\u0000\u0000\u0007\u013a"+ - "\u0001\u0000\u0000\u0000\u0007\u013c\u0001\u0000\u0000\u0000\u0007\u013e"+ - "\u0001\u0000\u0000\u0000\u0007\u0140\u0001\u0000\u0000\u0000\u0007\u0142"+ - "\u0001\u0000\u0000\u0000\u0007\u0146\u0001\u0000\u0000\u0000\u0007\u0148"+ - "\u0001\u0000\u0000\u0000\u0007\u014a\u0001\u0000\u0000\u0000\u0007\u014c"+ - "\u0001\u0000\u0000\u0000\u0007\u014e\u0001\u0000\u0000\u0000\u0007\u0150"+ - "\u0001\u0000\u0000\u0000\b\u0152\u0001\u0000\u0000\u0000\b\u0154\u0001"+ - "\u0000\u0000\u0000\b\u0156\u0001\u0000\u0000\u0000\b\u0158\u0001\u0000"+ - "\u0000\u0000\b\u015a\u0001\u0000\u0000\u0000\t\u015c\u0001\u0000\u0000"+ - "\u0000\t\u015e\u0001\u0000\u0000\u0000\t\u0160\u0001\u0000\u0000\u0000"+ - "\t\u0162\u0001\u0000\u0000\u0000\t\u0164\u0001\u0000\u0000\u0000\t\u0166"+ - "\u0001\u0000\u0000\u0000\t\u0168\u0001\u0000\u0000\u0000\t\u016a\u0001"+ - "\u0000\u0000\u0000\t\u016c\u0001\u0000\u0000\u0000\t\u016e\u0001\u0000"+ - "\u0000\u0000\t\u0170\u0001\u0000\u0000\u0000\t\u0172\u0001\u0000\u0000"+ - "\u0000\t\u0174\u0001\u0000\u0000\u0000\n\u0176\u0001\u0000\u0000\u0000"+ - "\n\u0178\u0001\u0000\u0000\u0000\n\u017a\u0001\u0000\u0000\u0000\n\u017c"+ - "\u0001\u0000\u0000\u0000\n\u017e\u0001\u0000\u0000\u0000\n\u0180\u0001"+ - "\u0000\u0000\u0000\n\u0182\u0001\u0000\u0000\u0000\n\u0184\u0001\u0000"+ - "\u0000\u0000\n\u0186\u0001\u0000\u0000\u0000\n\u0188\u0001\u0000\u0000"+ - "\u0000\u000b\u018a\u0001\u0000\u0000\u0000\u000b\u018c\u0001\u0000\u0000"+ - "\u0000\u000b\u018e\u0001\u0000\u0000\u0000\u000b\u0190\u0001\u0000\u0000"+ - "\u0000\u000b\u0192\u0001\u0000\u0000\u0000\u000b\u0194\u0001\u0000\u0000"+ - "\u0000\u000b\u0196\u0001\u0000\u0000\u0000\f\u0198\u0001\u0000\u0000\u0000"+ - "\f\u019a\u0001\u0000\u0000\u0000\f\u019c\u0001\u0000\u0000\u0000\f\u019e"+ - "\u0001\u0000\u0000\u0000\f\u01a0\u0001\u0000\u0000\u0000\f\u01a2\u0001"+ - "\u0000\u0000\u0000\f\u01a4\u0001\u0000\u0000\u0000\f\u01a6\u0001\u0000"+ - "\u0000\u0000\f\u01a8\u0001\u0000\u0000\u0000\f\u01aa\u0001\u0000\u0000"+ - "\u0000\f\u01ac\u0001\u0000\u0000\u0000\r\u01ae\u0001\u0000\u0000\u0000"+ - "\r\u01b0\u0001\u0000\u0000\u0000\r\u01b2\u0001\u0000\u0000\u0000\r\u01b4"+ - "\u0001\u0000\u0000\u0000\r\u01b6\u0001\u0000\u0000\u0000\r\u01b8\u0001"+ - "\u0000\u0000\u0000\r\u01ba\u0001\u0000\u0000\u0000\r\u01c0\u0001\u0000"+ - "\u0000\u0000\r\u01c2\u0001\u0000\u0000\u0000\r\u01c4\u0001\u0000\u0000"+ - "\u0000\r\u01c6\u0001\u0000\u0000\u0000\u000e\u01c8\u0001\u0000\u0000\u0000"+ - "\u000e\u01ca\u0001\u0000\u0000\u0000\u000e\u01cc\u0001\u0000\u0000\u0000"+ - "\u000e\u01ce\u0001\u0000\u0000\u0000\u000e\u01d0\u0001\u0000\u0000\u0000"+ - "\u000e\u01d2\u0001\u0000\u0000\u0000\u000e\u01d4\u0001\u0000\u0000\u0000"+ - "\u000e\u01d6\u0001\u0000\u0000\u0000\u000e\u01d8\u0001\u0000\u0000\u0000"+ - "\u000e\u01da\u0001\u0000\u0000\u0000\u000e\u01dc\u0001\u0000\u0000\u0000"+ - "\u000e\u01de\u0001\u0000\u0000\u0000\u000e\u01e0\u0001\u0000\u0000\u0000"+ - "\u000f\u01e2\u0001\u0000\u0000\u0000\u000f\u01e4\u0001\u0000\u0000\u0000"+ - "\u000f\u01e6\u0001\u0000\u0000\u0000\u000f\u01e8\u0001\u0000\u0000\u0000"+ - "\u000f\u01ea\u0001\u0000\u0000\u0000\u0010\u01ec\u0001\u0000\u0000\u0000"+ - "\u0012\u01fd\u0001\u0000\u0000\u0000\u0014\u020d\u0001\u0000\u0000\u0000"+ - "\u0016\u0213\u0001\u0000\u0000\u0000\u0018\u0222\u0001\u0000\u0000\u0000"+ - "\u001a\u022b\u0001\u0000\u0000\u0000\u001c\u0235\u0001\u0000\u0000\u0000"+ - "\u001e\u0242\u0001\u0000\u0000\u0000 \u024c\u0001\u0000\u0000\u0000\""+ - "\u0253\u0001\u0000\u0000\u0000$\u025a\u0001\u0000\u0000\u0000&\u0262\u0001"+ - "\u0000\u0000\u0000(\u0268\u0001\u0000\u0000\u0000*\u026f\u0001\u0000\u0000"+ - "\u0000,\u0277\u0001\u0000\u0000\u0000.\u027f\u0001\u0000\u0000\u00000"+ - "\u028e\u0001\u0000\u0000\u00002\u0298\u0001\u0000\u0000\u00004\u02a2\u0001"+ - "\u0000\u0000\u00006\u02a9\u0001\u0000\u0000\u00008\u02af\u0001\u0000\u0000"+ - "\u0000:\u02b7\u0001\u0000\u0000\u0000<\u02c0\u0001\u0000\u0000\u0000>"+ - "\u02c8\u0001\u0000\u0000\u0000@\u02d0\u0001\u0000\u0000\u0000B\u02d9\u0001"+ - "\u0000\u0000\u0000D\u02e5\u0001\u0000\u0000\u0000F\u02f1\u0001\u0000\u0000"+ - "\u0000H\u02f8\u0001\u0000\u0000\u0000J\u02ff\u0001\u0000\u0000\u0000L"+ - "\u030b\u0001\u0000\u0000\u0000N\u0312\u0001\u0000\u0000\u0000P\u031b\u0001"+ - "\u0000\u0000\u0000R\u0323\u0001\u0000\u0000\u0000T\u0329\u0001\u0000\u0000"+ - "\u0000V\u032e\u0001\u0000\u0000\u0000X\u0332\u0001\u0000\u0000\u0000Z"+ - "\u0336\u0001\u0000\u0000\u0000\\\u033a\u0001\u0000\u0000\u0000^\u033e"+ - "\u0001\u0000\u0000\u0000`\u0342\u0001\u0000\u0000\u0000b\u0346\u0001\u0000"+ - "\u0000\u0000d\u034a\u0001\u0000\u0000\u0000f\u034e\u0001\u0000\u0000\u0000"+ - "h\u0352\u0001\u0000\u0000\u0000j\u0357\u0001\u0000\u0000\u0000l\u035c"+ - "\u0001\u0000\u0000\u0000n\u0361\u0001\u0000\u0000\u0000p\u0366\u0001\u0000"+ - "\u0000\u0000r\u036f\u0001\u0000\u0000\u0000t\u0376\u0001\u0000\u0000\u0000"+ - "v\u037a\u0001\u0000\u0000\u0000x\u037e\u0001\u0000\u0000\u0000z\u0382"+ - "\u0001\u0000\u0000\u0000|\u0386\u0001\u0000\u0000\u0000~\u038c\u0001\u0000"+ - "\u0000\u0000\u0080\u0390\u0001\u0000\u0000\u0000\u0082\u0394\u0001\u0000"+ - "\u0000\u0000\u0084\u0398\u0001\u0000\u0000\u0000\u0086\u039c\u0001\u0000"+ - "\u0000\u0000\u0088\u03a0\u0001\u0000\u0000\u0000\u008a\u03a4\u0001\u0000"+ - "\u0000\u0000\u008c\u03a8\u0001\u0000\u0000\u0000\u008e\u03ac\u0001\u0000"+ - "\u0000\u0000\u0090\u03b0\u0001\u0000\u0000\u0000\u0092\u03b4\u0001\u0000"+ - "\u0000\u0000\u0094\u03b8\u0001\u0000\u0000\u0000\u0096\u03bc\u0001\u0000"+ - "\u0000\u0000\u0098\u03c0\u0001\u0000\u0000\u0000\u009a\u03c5\u0001\u0000"+ - "\u0000\u0000\u009c\u03ce\u0001\u0000\u0000\u0000\u009e\u03d2\u0001\u0000"+ - "\u0000\u0000\u00a0\u03d6\u0001\u0000\u0000\u0000\u00a2\u03da\u0001\u0000"+ - "\u0000\u0000\u00a4\u03de\u0001\u0000\u0000\u0000\u00a6\u03e3\u0001\u0000"+ - "\u0000\u0000\u00a8\u03e8\u0001\u0000\u0000\u0000\u00aa\u03ec\u0001\u0000"+ - "\u0000\u0000\u00ac\u03f0\u0001\u0000\u0000\u0000\u00ae\u03f4\u0001\u0000"+ - "\u0000\u0000\u00b0\u03f8\u0001\u0000\u0000\u0000\u00b2\u03fa\u0001\u0000"+ - "\u0000\u0000\u00b4\u03fc\u0001\u0000\u0000\u0000\u00b6\u03ff\u0001\u0000"+ - "\u0000\u0000\u00b8\u0401\u0001\u0000\u0000\u0000\u00ba\u040a\u0001\u0000"+ - "\u0000\u0000\u00bc\u040c\u0001\u0000\u0000\u0000\u00be\u0411\u0001\u0000"+ - "\u0000\u0000\u00c0\u0413\u0001\u0000\u0000\u0000\u00c2\u0418\u0001\u0000"+ - "\u0000\u0000\u00c4\u0437\u0001\u0000\u0000\u0000\u00c6\u043a\u0001\u0000"+ - "\u0000\u0000\u00c8\u0468\u0001\u0000\u0000\u0000\u00ca\u046a\u0001\u0000"+ - "\u0000\u0000\u00cc\u046e\u0001\u0000\u0000\u0000\u00ce\u0471\u0001\u0000"+ - "\u0000\u0000\u00d0\u0475\u0001\u0000\u0000\u0000\u00d2\u0477\u0001\u0000"+ - "\u0000\u0000\u00d4\u047a\u0001\u0000\u0000\u0000\u00d6\u047d\u0001\u0000"+ - "\u0000\u0000\u00d8\u047f\u0001\u0000\u0000\u0000\u00da\u0481\u0001\u0000"+ - "\u0000\u0000\u00dc\u0486\u0001\u0000\u0000\u0000\u00de\u0488\u0001\u0000"+ - "\u0000\u0000\u00e0\u048e\u0001\u0000\u0000\u0000\u00e2\u0494\u0001\u0000"+ - "\u0000\u0000\u00e4\u0497\u0001\u0000\u0000\u0000\u00e6\u049a\u0001\u0000"+ - "\u0000\u0000\u00e8\u049f\u0001\u0000\u0000\u0000\u00ea\u04a4\u0001\u0000"+ - "\u0000\u0000\u00ec\u04a8\u0001\u0000\u0000\u0000\u00ee\u04ad\u0001\u0000"+ - "\u0000\u0000\u00f0\u04b3\u0001\u0000\u0000\u0000\u00f2\u04b6\u0001\u0000"+ - "\u0000\u0000\u00f4\u04b9\u0001\u0000\u0000\u0000\u00f6\u04bb\u0001\u0000"+ - "\u0000\u0000\u00f8\u04c1\u0001\u0000\u0000\u0000\u00fa\u04c6\u0001\u0000"+ - "\u0000\u0000\u00fc\u04cb\u0001\u0000\u0000\u0000\u00fe\u04ce\u0001\u0000"+ - "\u0000\u0000\u0100\u04d1\u0001\u0000\u0000\u0000\u0102\u04d4\u0001\u0000"+ - "\u0000\u0000\u0104\u04d6\u0001\u0000\u0000\u0000\u0106\u04d9\u0001\u0000"+ - "\u0000\u0000\u0108\u04db\u0001\u0000\u0000\u0000\u010a\u04de\u0001\u0000"+ - "\u0000\u0000\u010c\u04e0\u0001\u0000\u0000\u0000\u010e\u04e2\u0001\u0000"+ - "\u0000\u0000\u0110\u04e4\u0001\u0000\u0000\u0000\u0112\u04e6\u0001\u0000"+ - "\u0000\u0000\u0114\u04e8\u0001\u0000\u0000\u0000\u0116\u04ea\u0001\u0000"+ - "\u0000\u0000\u0118\u04ec\u0001\u0000\u0000\u0000\u011a\u04ef\u0001\u0000"+ - "\u0000\u0000\u011c\u0504\u0001\u0000\u0000\u0000\u011e\u0517\u0001\u0000"+ - "\u0000\u0000\u0120\u0519\u0001\u0000\u0000\u0000\u0122\u051e\u0001\u0000"+ - "\u0000\u0000\u0124\u0523\u0001\u0000\u0000\u0000\u0126\u0528\u0001\u0000"+ - "\u0000\u0000\u0128\u053d\u0001\u0000\u0000\u0000\u012a\u053f\u0001\u0000"+ - "\u0000\u0000\u012c\u0547\u0001\u0000\u0000\u0000\u012e\u0549\u0001\u0000"+ - "\u0000\u0000\u0130\u054d\u0001\u0000\u0000\u0000\u0132\u0551\u0001\u0000"+ - "\u0000\u0000\u0134\u0555\u0001\u0000\u0000\u0000\u0136\u055a\u0001\u0000"+ - "\u0000\u0000\u0138\u055e\u0001\u0000\u0000\u0000\u013a\u0562\u0001\u0000"+ - "\u0000\u0000\u013c\u0566\u0001\u0000\u0000\u0000\u013e\u056a\u0001\u0000"+ - "\u0000\u0000\u0140\u056e\u0001\u0000\u0000\u0000\u0142\u0572\u0001\u0000"+ - "\u0000\u0000\u0144\u057e\u0001\u0000\u0000\u0000\u0146\u0581\u0001\u0000"+ - "\u0000\u0000\u0148\u0585\u0001\u0000\u0000\u0000\u014a\u0589\u0001\u0000"+ - "\u0000\u0000\u014c\u058d\u0001\u0000\u0000\u0000\u014e\u0591\u0001\u0000"+ - "\u0000\u0000\u0150\u0595\u0001\u0000\u0000\u0000\u0152\u0599\u0001\u0000"+ - "\u0000\u0000\u0154\u059e\u0001\u0000\u0000\u0000\u0156\u05a3\u0001\u0000"+ - "\u0000\u0000\u0158\u05a7\u0001\u0000\u0000\u0000\u015a\u05ab\u0001\u0000"+ - "\u0000\u0000\u015c\u05af\u0001\u0000\u0000\u0000\u015e\u05b4\u0001\u0000"+ - "\u0000\u0000\u0160\u05b9\u0001\u0000\u0000\u0000\u0162\u05bd\u0001\u0000"+ - "\u0000\u0000\u0164\u05c3\u0001\u0000\u0000\u0000\u0166\u05cc\u0001\u0000"+ - "\u0000\u0000\u0168\u05d0\u0001\u0000\u0000\u0000\u016a\u05d4\u0001\u0000"+ - "\u0000\u0000\u016c\u05d8\u0001\u0000\u0000\u0000\u016e\u05dc\u0001\u0000"+ - "\u0000\u0000\u0170\u05e0\u0001\u0000\u0000\u0000\u0172\u05e4\u0001\u0000"+ - "\u0000\u0000\u0174\u05e8\u0001\u0000\u0000\u0000\u0176\u05ec\u0001\u0000"+ - "\u0000\u0000\u0178\u05f1\u0001\u0000\u0000\u0000\u017a\u05f5\u0001\u0000"+ - "\u0000\u0000\u017c\u05f9\u0001\u0000\u0000\u0000\u017e\u05fd\u0001\u0000"+ - "\u0000\u0000\u0180\u0602\u0001\u0000\u0000\u0000\u0182\u0606\u0001\u0000"+ - "\u0000\u0000\u0184\u060a\u0001\u0000\u0000\u0000\u0186\u060e\u0001\u0000"+ - "\u0000\u0000\u0188\u0612\u0001\u0000\u0000\u0000\u018a\u0616\u0001\u0000"+ - "\u0000\u0000\u018c\u061c\u0001\u0000\u0000\u0000\u018e\u0620\u0001\u0000"+ - "\u0000\u0000\u0190\u0624\u0001\u0000\u0000\u0000\u0192\u0628\u0001\u0000"+ - "\u0000\u0000\u0194\u062c\u0001\u0000\u0000\u0000\u0196\u0630\u0001\u0000"+ - "\u0000\u0000\u0198\u0634\u0001\u0000\u0000\u0000\u019a\u0639\u0001\u0000"+ - "\u0000\u0000\u019c\u063d\u0001\u0000\u0000\u0000\u019e\u0641\u0001\u0000"+ - "\u0000\u0000\u01a0\u0645\u0001\u0000\u0000\u0000\u01a2\u0649\u0001\u0000"+ - "\u0000\u0000\u01a4\u064d\u0001\u0000\u0000\u0000\u01a6\u0651\u0001\u0000"+ - "\u0000\u0000\u01a8\u0655\u0001\u0000\u0000\u0000\u01aa\u0659\u0001\u0000"+ - "\u0000\u0000\u01ac\u065d\u0001\u0000\u0000\u0000\u01ae\u0661\u0001\u0000"+ - "\u0000\u0000\u01b0\u0666\u0001\u0000\u0000\u0000\u01b2\u066a\u0001\u0000"+ - "\u0000\u0000\u01b4\u066e\u0001\u0000\u0000\u0000\u01b6\u0672\u0001\u0000"+ - "\u0000\u0000\u01b8\u0676\u0001\u0000\u0000\u0000\u01ba\u067a\u0001\u0000"+ - "\u0000\u0000\u01bc\u0682\u0001\u0000\u0000\u0000\u01be\u0697\u0001\u0000"+ - "\u0000\u0000\u01c0\u069b\u0001\u0000\u0000\u0000\u01c2\u069f\u0001\u0000"+ - "\u0000\u0000\u01c4\u06a3\u0001\u0000\u0000\u0000\u01c6\u06a7\u0001\u0000"+ - "\u0000\u0000\u01c8\u06ab\u0001\u0000\u0000\u0000\u01ca\u06b0\u0001\u0000"+ - "\u0000\u0000\u01cc\u06b4\u0001\u0000\u0000\u0000\u01ce\u06b8\u0001\u0000"+ - "\u0000\u0000\u01d0\u06bc\u0001\u0000\u0000\u0000\u01d2\u06c0\u0001\u0000"+ - "\u0000\u0000\u01d4\u06c4\u0001\u0000\u0000\u0000\u01d6\u06c8\u0001\u0000"+ - "\u0000\u0000\u01d8\u06cc\u0001\u0000\u0000\u0000\u01da\u06d0\u0001\u0000"+ - "\u0000\u0000\u01dc\u06d4\u0001\u0000\u0000\u0000\u01de\u06d8\u0001\u0000"+ - "\u0000\u0000\u01e0\u06dc\u0001\u0000\u0000\u0000\u01e2\u06e0\u0001\u0000"+ - "\u0000\u0000\u01e4\u06e5\u0001\u0000\u0000\u0000\u01e6\u06ea\u0001\u0000"+ - "\u0000\u0000\u01e8\u06ee\u0001\u0000\u0000\u0000\u01ea\u06f2\u0001\u0000"+ - "\u0000\u0000\u01ec\u01ed\u0005/\u0000\u0000\u01ed\u01ee\u0005/\u0000\u0000"+ - "\u01ee\u01f2\u0001\u0000\u0000\u0000\u01ef\u01f1\b\u0000\u0000\u0000\u01f0"+ - "\u01ef\u0001\u0000\u0000\u0000\u01f1\u01f4\u0001\u0000\u0000\u0000\u01f2"+ - "\u01f0\u0001\u0000\u0000\u0000\u01f2\u01f3\u0001\u0000\u0000\u0000\u01f3"+ - "\u01f6\u0001\u0000\u0000\u0000\u01f4\u01f2\u0001\u0000\u0000\u0000\u01f5"+ - "\u01f7\u0005\r\u0000\u0000\u01f6\u01f5\u0001\u0000\u0000\u0000\u01f6\u01f7"+ - "\u0001\u0000\u0000\u0000\u01f7\u01f9\u0001\u0000\u0000\u0000\u01f8\u01fa"+ - "\u0005\n\u0000\u0000\u01f9\u01f8\u0001\u0000\u0000\u0000\u01f9\u01fa\u0001"+ - "\u0000\u0000\u0000\u01fa\u01fb\u0001\u0000\u0000\u0000\u01fb\u01fc\u0006"+ - "\u0000\u0000\u0000\u01fc\u0011\u0001\u0000\u0000\u0000\u01fd\u01fe\u0005"+ - "/\u0000\u0000\u01fe\u01ff\u0005*\u0000\u0000\u01ff\u0204\u0001\u0000\u0000"+ - "\u0000\u0200\u0203\u0003\u0012\u0001\u0000\u0201\u0203\t\u0000\u0000\u0000"+ - "\u0202\u0200\u0001\u0000\u0000\u0000\u0202\u0201\u0001\u0000\u0000\u0000"+ - "\u0203\u0206\u0001\u0000\u0000\u0000\u0204\u0205\u0001\u0000\u0000\u0000"+ - "\u0204\u0202\u0001\u0000\u0000\u0000\u0205\u0207\u0001\u0000\u0000\u0000"+ - "\u0206\u0204\u0001\u0000\u0000\u0000\u0207\u0208\u0005*\u0000\u0000\u0208"+ - "\u0209\u0005/\u0000\u0000\u0209\u020a\u0001\u0000\u0000\u0000\u020a\u020b"+ - "\u0006\u0001\u0000\u0000\u020b\u0013\u0001\u0000\u0000\u0000\u020c\u020e"+ - "\u0007\u0001\u0000\u0000\u020d\u020c\u0001\u0000\u0000\u0000\u020e\u020f"+ - "\u0001\u0000\u0000\u0000\u020f\u020d\u0001\u0000\u0000\u0000\u020f\u0210"+ - "\u0001\u0000\u0000\u0000\u0210\u0211\u0001\u0000\u0000\u0000\u0211\u0212"+ - "\u0006\u0002\u0000\u0000\u0212\u0015\u0001\u0000\u0000\u0000\u0213\u0214"+ - "\u0007\u0002\u0000\u0000\u0214\u0215\u0007\u0003\u0000\u0000\u0215\u0216"+ - "\u0007\u0004\u0000\u0000\u0216\u0217\u0007\u0005\u0000\u0000\u0217\u0218"+ - "\u0007\u0006\u0000\u0000\u0218\u0219\u0007\u0007\u0000\u0000\u0219\u021a"+ - "\u0005_\u0000\u0000\u021a\u021b\u0007\b\u0000\u0000\u021b\u021c\u0007"+ - "\t\u0000\u0000\u021c\u021d\u0007\n\u0000\u0000\u021d\u021e\u0007\u0005"+ - "\u0000\u0000\u021e\u021f\u0007\u000b\u0000\u0000\u021f\u0220\u0001\u0000"+ - "\u0000\u0000\u0220\u0221\u0006\u0003\u0001\u0000\u0221\u0017\u0001\u0000"+ - "\u0000\u0000\u0222\u0223\u0007\u0007\u0000\u0000\u0223\u0224\u0007\u0005"+ - "\u0000\u0000\u0224\u0225\u0007\f\u0000\u0000\u0225\u0226\u0007\n\u0000"+ - "\u0000\u0226\u0227\u0007\u0002\u0000\u0000\u0227\u0228\u0007\u0003\u0000"+ - "\u0000\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0006\u0004\u0002"+ - "\u0000\u022a\u0019\u0001\u0000\u0000\u0000\u022b\u022c\u0007\u0007\u0000"+ - "\u0000\u022c\u022d\u0007\r\u0000\u0000\u022d\u022e\u0007\b\u0000\u0000"+ - "\u022e\u022f\u0007\u000e\u0000\u0000\u022f\u0230\u0007\u0004\u0000\u0000"+ - "\u0230\u0231\u0007\n\u0000\u0000\u0231\u0232\u0007\u0005\u0000\u0000\u0232"+ - "\u0233\u0001\u0000\u0000\u0000\u0233\u0234\u0006\u0005\u0003\u0000\u0234"+ - "\u001b\u0001\u0000\u0000\u0000\u0235\u0236\u0007\u0002\u0000\u0000\u0236"+ - "\u0237\u0007\t\u0000\u0000\u0237\u0238\u0007\u000f\u0000\u0000\u0238\u0239"+ - "\u0007\b\u0000\u0000\u0239\u023a\u0007\u000e\u0000\u0000\u023a\u023b\u0007"+ - "\u0007\u0000\u0000\u023b\u023c\u0007\u000b\u0000\u0000\u023c\u023d\u0007"+ - "\n\u0000\u0000\u023d\u023e\u0007\t\u0000\u0000\u023e\u023f\u0007\u0005"+ - "\u0000\u0000\u023f\u0240\u0001\u0000\u0000\u0000\u0240\u0241\u0006\u0006"+ - "\u0004\u0000\u0241\u001d\u0001\u0000\u0000\u0000\u0242\u0243\u0007\u0010"+ - "\u0000\u0000\u0243\u0244\u0007\n\u0000\u0000\u0244\u0245\u0007\u0011\u0000"+ - "\u0000\u0245\u0246\u0007\u0011\u0000\u0000\u0246\u0247\u0007\u0007\u0000"+ - "\u0000\u0247\u0248\u0007\u0002\u0000\u0000\u0248\u0249\u0007\u000b\u0000"+ - "\u0000\u0249\u024a\u0001\u0000\u0000\u0000\u024a\u024b\u0006\u0007\u0004"+ - "\u0000\u024b\u001f\u0001\u0000\u0000\u0000\u024c\u024d\u0007\u0007\u0000"+ - "\u0000\u024d\u024e\u0007\u0012\u0000\u0000\u024e\u024f\u0007\u0004\u0000"+ - "\u0000\u024f\u0250\u0007\u000e\u0000\u0000\u0250\u0251\u0001\u0000\u0000"+ - "\u0000\u0251\u0252\u0006\b\u0004\u0000\u0252!\u0001\u0000\u0000\u0000"+ - "\u0253\u0254\u0007\u0006\u0000\u0000\u0254\u0255\u0007\f\u0000\u0000\u0255"+ - "\u0256\u0007\t\u0000\u0000\u0256\u0257\u0007\u0013\u0000\u0000\u0257\u0258"+ - "\u0001\u0000\u0000\u0000\u0258\u0259\u0006\t\u0004\u0000\u0259#\u0001"+ - "\u0000\u0000\u0000\u025a\u025b\u0007\u000e\u0000\u0000\u025b\u025c\u0007"+ - "\n\u0000\u0000\u025c\u025d\u0007\u000f\u0000\u0000\u025d\u025e\u0007\n"+ - "\u0000\u0000\u025e\u025f\u0007\u000b\u0000\u0000\u025f\u0260\u0001\u0000"+ - "\u0000\u0000\u0260\u0261\u0006\n\u0004\u0000\u0261%\u0001\u0000\u0000"+ - "\u0000\u0262\u0263\u0007\f\u0000\u0000\u0263\u0264\u0007\t\u0000\u0000"+ - "\u0264\u0265\u0007\u0014\u0000\u0000\u0265\u0266\u0001\u0000\u0000\u0000"+ - "\u0266\u0267\u0006\u000b\u0004\u0000\u0267\'\u0001\u0000\u0000\u0000\u0268"+ - "\u0269\u0007\u0011\u0000\u0000\u0269\u026a\u0007\t\u0000\u0000\u026a\u026b"+ - "\u0007\f\u0000\u0000\u026b\u026c\u0007\u000b\u0000\u0000\u026c\u026d\u0001"+ - "\u0000\u0000\u0000\u026d\u026e\u0006\f\u0004\u0000\u026e)\u0001\u0000"+ - "\u0000\u0000\u026f\u0270\u0007\u0011\u0000\u0000\u0270\u0271\u0007\u000b"+ - "\u0000\u0000\u0271\u0272\u0007\u0004\u0000\u0000\u0272\u0273\u0007\u000b"+ - "\u0000\u0000\u0273\u0274\u0007\u0011\u0000\u0000\u0274\u0275\u0001\u0000"+ - "\u0000\u0000\u0275\u0276\u0006\r\u0004\u0000\u0276+\u0001\u0000\u0000"+ - "\u0000\u0277\u0278\u0007\u0014\u0000\u0000\u0278\u0279\u0007\u0003\u0000"+ - "\u0000\u0279\u027a\u0007\u0007\u0000\u0000\u027a\u027b\u0007\f\u0000\u0000"+ - "\u027b\u027c\u0007\u0007\u0000\u0000\u027c\u027d\u0001\u0000\u0000\u0000"+ - "\u027d\u027e\u0006\u000e\u0004\u0000\u027e-\u0001\u0000\u0000\u0000\u027f"+ - "\u0280\u0004\u000f\u0000\u0000\u0280\u0281\u0007\n\u0000\u0000\u0281\u0282"+ - "\u0007\u0005\u0000\u0000\u0282\u0283\u0007\u000e\u0000\u0000\u0283\u0284"+ - "\u0007\n\u0000\u0000\u0284\u0285\u0007\u0005\u0000\u0000\u0285\u0286\u0007"+ - "\u0007\u0000\u0000\u0286\u0287\u0007\u0011\u0000\u0000\u0287\u0288\u0007"+ - "\u000b\u0000\u0000\u0288\u0289\u0007\u0004\u0000\u0000\u0289\u028a\u0007"+ - "\u000b\u0000\u0000\u028a\u028b\u0007\u0011\u0000\u0000\u028b\u028c\u0001"+ - "\u0000\u0000\u0000\u028c\u028d\u0006\u000f\u0004\u0000\u028d/\u0001\u0000"+ - "\u0000\u0000\u028e\u028f\u0004\u0010\u0001\u0000\u028f\u0290\u0007\f\u0000"+ - "\u0000\u0290\u0291\u0007\u0007\u0000\u0000\u0291\u0292\u0007\f\u0000\u0000"+ - "\u0292\u0293\u0007\u0004\u0000\u0000\u0293\u0294\u0007\u0005\u0000\u0000"+ - "\u0294\u0295\u0007\u0013\u0000\u0000\u0295\u0296\u0001\u0000\u0000\u0000"+ - "\u0296\u0297\u0006\u0010\u0004\u0000\u02971\u0001\u0000\u0000\u0000\u0298"+ - "\u0299\u0004\u0011\u0002\u0000\u0299\u029a\u0007\u0011\u0000\u0000\u029a"+ - "\u029b\u0007\u0004\u0000\u0000\u029b\u029c\u0007\u000f\u0000\u0000\u029c"+ - "\u029d\u0007\b\u0000\u0000\u029d\u029e\u0007\u000e\u0000\u0000\u029e\u029f"+ - "\u0007\u0007\u0000\u0000\u029f\u02a0\u0001\u0000\u0000\u0000\u02a0\u02a1"+ - "\u0006\u0011\u0004\u0000\u02a13\u0001\u0000\u0000\u0000\u02a2\u02a3\u0007"+ - "\u0015\u0000\u0000\u02a3\u02a4\u0007\f\u0000\u0000\u02a4\u02a5\u0007\t"+ - "\u0000\u0000\u02a5\u02a6\u0007\u000f\u0000\u0000\u02a6\u02a7\u0001\u0000"+ - "\u0000\u0000\u02a7\u02a8\u0006\u0012\u0005\u0000\u02a85\u0001\u0000\u0000"+ - "\u0000\u02a9\u02aa\u0004\u0013\u0003\u0000\u02aa\u02ab\u0007\u000b\u0000"+ - "\u0000\u02ab\u02ac\u0007\u0011\u0000\u0000\u02ac\u02ad\u0001\u0000\u0000"+ - "\u0000\u02ad\u02ae\u0006\u0013\u0005\u0000\u02ae7\u0001\u0000\u0000\u0000"+ - "\u02af\u02b0\u0004\u0014\u0004\u0000\u02b0\u02b1\u0007\u0015\u0000\u0000"+ - "\u02b1\u02b2\u0007\t\u0000\u0000\u02b2\u02b3\u0007\f\u0000\u0000\u02b3"+ - "\u02b4\u0007\u0013\u0000\u0000\u02b4\u02b5\u0001\u0000\u0000\u0000\u02b5"+ - "\u02b6\u0006\u0014\u0006\u0000\u02b69\u0001\u0000\u0000\u0000\u02b7\u02b8"+ - "\u0007\u000e\u0000\u0000\u02b8\u02b9\u0007\t\u0000\u0000\u02b9\u02ba\u0007"+ - "\t\u0000\u0000\u02ba\u02bb\u0007\u0013\u0000\u0000\u02bb\u02bc\u0007\u0016"+ - "\u0000\u0000\u02bc\u02bd\u0007\b\u0000\u0000\u02bd\u02be\u0001\u0000\u0000"+ - "\u0000\u02be\u02bf\u0006\u0015\u0007\u0000\u02bf;\u0001\u0000\u0000\u0000"+ - "\u02c0\u02c1\u0004\u0016\u0005\u0000\u02c1\u02c2\u0007\u0015\u0000\u0000"+ - "\u02c2\u02c3\u0007\u0016\u0000\u0000\u02c3\u02c4\u0007\u000e\u0000\u0000"+ - "\u02c4\u02c5\u0007\u000e\u0000\u0000\u02c5\u02c6\u0001\u0000\u0000\u0000"+ - "\u02c6\u02c7\u0006\u0016\u0007\u0000\u02c7=\u0001\u0000\u0000\u0000\u02c8"+ - "\u02c9\u0004\u0017\u0006\u0000\u02c9\u02ca\u0007\u000e\u0000\u0000\u02ca"+ - "\u02cb\u0007\u0007\u0000\u0000\u02cb\u02cc\u0007\u0015\u0000\u0000\u02cc"+ - "\u02cd\u0007\u000b\u0000\u0000\u02cd\u02ce\u0001\u0000\u0000\u0000\u02ce"+ - "\u02cf\u0006\u0017\u0007\u0000\u02cf?\u0001\u0000\u0000\u0000\u02d0\u02d1"+ - "\u0004\u0018\u0007\u0000\u02d1\u02d2\u0007\f\u0000\u0000\u02d2\u02d3\u0007"+ - "\n\u0000\u0000\u02d3\u02d4\u0007\u0006\u0000\u0000\u02d4\u02d5\u0007\u0003"+ - "\u0000\u0000\u02d5\u02d6\u0007\u000b\u0000\u0000\u02d6\u02d7\u0001\u0000"+ - "\u0000\u0000\u02d7\u02d8\u0006\u0018\u0007\u0000\u02d8A\u0001\u0000\u0000"+ - "\u0000\u02d9\u02da\u0004\u0019\b\u0000\u02da\u02db\u0007\u000e\u0000\u0000"+ - "\u02db\u02dc\u0007\t\u0000\u0000\u02dc\u02dd\u0007\t\u0000\u0000\u02dd"+ - "\u02de\u0007\u0013\u0000\u0000\u02de\u02df\u0007\u0016\u0000\u0000\u02df"+ - "\u02e0\u0007\b\u0000\u0000\u02e0\u02e1\u0005_\u0000\u0000\u02e1\u02e2"+ - "\u0005\u8001\uf414\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ - "\u02e4\u0006\u0019\b\u0000\u02e4C\u0001\u0000\u0000\u0000\u02e5\u02e6"+ - "\u0007\u000f\u0000\u0000\u02e6\u02e7\u0007\u0012\u0000\u0000\u02e7\u02e8"+ - "\u0005_\u0000\u0000\u02e8\u02e9\u0007\u0007\u0000\u0000\u02e9\u02ea\u0007"+ - "\r\u0000\u0000\u02ea\u02eb\u0007\b\u0000\u0000\u02eb\u02ec\u0007\u0004"+ - "\u0000\u0000\u02ec\u02ed\u0007\u0005\u0000\u0000\u02ed\u02ee\u0007\u0010"+ - "\u0000\u0000\u02ee\u02ef\u0001\u0000\u0000\u0000\u02ef\u02f0\u0006\u001a"+ - "\t\u0000\u02f0E\u0001\u0000\u0000\u0000\u02f1\u02f2\u0007\u0010\u0000"+ - "\u0000\u02f2\u02f3\u0007\f\u0000\u0000\u02f3\u02f4\u0007\t\u0000\u0000"+ - "\u02f4\u02f5\u0007\b\u0000\u0000\u02f5\u02f6\u0001\u0000\u0000\u0000\u02f6"+ - "\u02f7\u0006\u001b\n\u0000\u02f7G\u0001\u0000\u0000\u0000\u02f8\u02f9"+ - "\u0007\u0013\u0000\u0000\u02f9\u02fa\u0007\u0007\u0000\u0000\u02fa\u02fb"+ - "\u0007\u0007\u0000\u0000\u02fb\u02fc\u0007\b\u0000\u0000\u02fc\u02fd\u0001"+ - "\u0000\u0000\u0000\u02fd\u02fe\u0006\u001c\n\u0000\u02feI\u0001\u0000"+ - "\u0000\u0000\u02ff\u0300\u0004\u001d\t\u0000\u0300\u0301\u0007\n\u0000"+ - "\u0000\u0301\u0302\u0007\u0005\u0000\u0000\u0302\u0303\u0007\u0011\u0000"+ - "\u0000\u0303\u0304\u0007\n\u0000\u0000\u0304\u0305\u0007\u0011\u0000\u0000"+ - "\u0305\u0306\u0007\u000b\u0000\u0000\u0306\u0307\u0005_\u0000\u0000\u0307"+ - "\u0308\u0005\u8001\uf414\u0000\u0000\u0308\u0309\u0001\u0000\u0000\u0000"+ - "\u0309\u030a\u0006\u001d\n\u0000\u030aK\u0001\u0000\u0000\u0000\u030b"+ - "\u030c\u0004\u001e\n\u0000\u030c\u030d\u0007\f\u0000\u0000\u030d\u030e"+ - "\u0007\f\u0000\u0000\u030e\u030f\u0007\u0015\u0000\u0000\u030f\u0310\u0001"+ - "\u0000\u0000\u0000\u0310\u0311\u0006\u001e\u0004\u0000\u0311M\u0001\u0000"+ - "\u0000\u0000\u0312\u0313\u0007\f\u0000\u0000\u0313\u0314\u0007\u0007\u0000"+ - "\u0000\u0314\u0315\u0007\u0005\u0000\u0000\u0315\u0316\u0007\u0004\u0000"+ - "\u0000\u0316\u0317\u0007\u000f\u0000\u0000\u0317\u0318\u0007\u0007\u0000"+ - "\u0000\u0318\u0319\u0001\u0000\u0000\u0000\u0319\u031a\u0006\u001f\u000b"+ - "\u0000\u031aO\u0001\u0000\u0000\u0000\u031b\u031c\u0007\u0011\u0000\u0000"+ - "\u031c\u031d\u0007\u0003\u0000\u0000\u031d\u031e\u0007\t\u0000\u0000\u031e"+ - "\u031f\u0007\u0014\u0000\u0000\u031f\u0320\u0001\u0000\u0000\u0000\u0320"+ - "\u0321\u0006 \f\u0000\u0321Q\u0001\u0000\u0000\u0000\u0322\u0324\b\u0017"+ - "\u0000\u0000\u0323\u0322\u0001\u0000\u0000\u0000\u0324\u0325\u0001\u0000"+ - "\u0000\u0000\u0325\u0323\u0001\u0000\u0000\u0000\u0325\u0326\u0001\u0000"+ - "\u0000\u0000\u0326\u0327\u0001\u0000\u0000\u0000\u0327\u0328\u0006!\u0004"+ - "\u0000\u0328S\u0001\u0000\u0000\u0000\u0329\u032a\u0003\u00aeO\u0000\u032a"+ - "\u032b\u0001\u0000\u0000\u0000\u032b\u032c\u0006\"\r\u0000\u032c\u032d"+ - "\u0006\"\u000e\u0000\u032dU\u0001\u0000\u0000\u0000\u032e\u032f\u0003"+ - "\u00f0p\u0000\u032f\u0330\u0001\u0000\u0000\u0000\u0330\u0331\u0006#\u000f"+ - "\u0000\u0331W\u0001\u0000\u0000\u0000\u0332\u0333\u0003\u00cc^\u0000\u0333"+ - "\u0334\u0001\u0000\u0000\u0000\u0334\u0335\u0006$\u0010\u0000\u0335Y\u0001"+ - "\u0000\u0000\u0000\u0336\u0337\u0003\u00dcf\u0000\u0337\u0338\u0001\u0000"+ - "\u0000\u0000\u0338\u0339\u0006%\u0011\u0000\u0339[\u0001\u0000\u0000\u0000"+ - "\u033a\u033b\u0003\u00d8d\u0000\u033b\u033c\u0001\u0000\u0000\u0000\u033c"+ - "\u033d\u0006&\u0012\u0000\u033d]\u0001\u0000\u0000\u0000\u033e\u033f\u0003"+ - "\u012c\u008e\u0000\u033f\u0340\u0001\u0000\u0000\u0000\u0340\u0341\u0006"+ - "\'\u0013\u0000\u0341_\u0001\u0000\u0000\u0000\u0342\u0343\u0003\u0128"+ - "\u008c\u0000\u0343\u0344\u0001\u0000\u0000\u0000\u0344\u0345\u0006(\u0014"+ - "\u0000\u0345a\u0001\u0000\u0000\u0000\u0346\u0347\u0003\u0010\u0000\u0000"+ - "\u0347\u0348\u0001\u0000\u0000\u0000\u0348\u0349\u0006)\u0000\u0000\u0349"+ - "c\u0001\u0000\u0000\u0000\u034a\u034b\u0003\u0012\u0001\u0000\u034b\u034c"+ - "\u0001\u0000\u0000\u0000\u034c\u034d\u0006*\u0000\u0000\u034de\u0001\u0000"+ - "\u0000\u0000\u034e\u034f\u0003\u0014\u0002\u0000\u034f\u0350\u0001\u0000"+ - "\u0000\u0000\u0350\u0351\u0006+\u0000\u0000\u0351g\u0001\u0000\u0000\u0000"+ - "\u0352\u0353\u0003\u00aeO\u0000\u0353\u0354\u0001\u0000\u0000\u0000\u0354"+ - "\u0355\u0006,\r\u0000\u0355\u0356\u0006,\u000e\u0000\u0356i\u0001\u0000"+ - "\u0000\u0000\u0357\u0358\u0003\u0120\u0088\u0000\u0358\u0359\u0001\u0000"+ - "\u0000\u0000\u0359\u035a\u0006-\u0015\u0000\u035a\u035b\u0006-\u0016\u0000"+ - "\u035bk\u0001\u0000\u0000\u0000\u035c\u035d\u0003\u00f0p\u0000\u035d\u035e"+ - "\u0001\u0000\u0000\u0000\u035e\u035f\u0006.\u000f\u0000\u035f\u0360\u0006"+ - ".\u0017\u0000\u0360m\u0001\u0000\u0000\u0000\u0361\u0362\u0003\u00fau"+ - "\u0000\u0362\u0363\u0001\u0000\u0000\u0000\u0363\u0364\u0006/\u0018\u0000"+ - "\u0364\u0365\u0006/\u0017\u0000\u0365o\u0001\u0000\u0000\u0000\u0366\u0367"+ - "\b\u0018\u0000\u0000\u0367q\u0001\u0000\u0000\u0000\u0368\u036a\u0003"+ - "p0\u0000\u0369\u0368\u0001\u0000\u0000\u0000\u036a\u036b\u0001\u0000\u0000"+ - "\u0000\u036b\u0369\u0001\u0000\u0000\u0000\u036b\u036c\u0001\u0000\u0000"+ - "\u0000\u036c\u036d\u0001\u0000\u0000\u0000\u036d\u036e\u0003\u00d6c\u0000"+ - "\u036e\u0370\u0001\u0000\u0000\u0000\u036f\u0369\u0001\u0000\u0000\u0000"+ - "\u036f\u0370\u0001\u0000\u0000\u0000\u0370\u0372\u0001\u0000\u0000\u0000"+ - "\u0371\u0373\u0003p0\u0000\u0372\u0371\u0001\u0000\u0000\u0000\u0373\u0374"+ - "\u0001\u0000\u0000\u0000\u0374\u0372\u0001\u0000\u0000\u0000\u0374\u0375"+ - "\u0001\u0000\u0000\u0000\u0375s\u0001\u0000\u0000\u0000\u0376\u0377\u0003"+ - "r1\u0000\u0377\u0378\u0001\u0000\u0000\u0000\u0378\u0379\u00062\u0019"+ - "\u0000\u0379u\u0001\u0000\u0000\u0000\u037a\u037b\u0003\u0010\u0000\u0000"+ - "\u037b\u037c\u0001\u0000\u0000\u0000\u037c\u037d\u00063\u0000\u0000\u037d"+ - "w\u0001\u0000\u0000\u0000\u037e\u037f\u0003\u0012\u0001\u0000\u037f\u0380"+ - "\u0001\u0000\u0000\u0000\u0380\u0381\u00064\u0000\u0000\u0381y\u0001\u0000"+ - "\u0000\u0000\u0382\u0383\u0003\u0014\u0002\u0000\u0383\u0384\u0001\u0000"+ - "\u0000\u0000\u0384\u0385\u00065\u0000\u0000\u0385{\u0001\u0000\u0000\u0000"+ - "\u0386\u0387\u0003\u00aeO\u0000\u0387\u0388\u0001\u0000\u0000\u0000\u0388"+ - "\u0389\u00066\r\u0000\u0389\u038a\u00066\u000e\u0000\u038a\u038b\u0006"+ - "6\u000e\u0000\u038b}\u0001\u0000\u0000\u0000\u038c\u038d\u0003\u00d0`"+ - "\u0000\u038d\u038e\u0001\u0000\u0000\u0000\u038e\u038f\u00067\u001a\u0000"+ - "\u038f\u007f\u0001\u0000\u0000\u0000\u0390\u0391\u0003\u00d8d\u0000\u0391"+ - "\u0392\u0001\u0000\u0000\u0000\u0392\u0393\u00068\u0012\u0000\u0393\u0081"+ - "\u0001\u0000\u0000\u0000\u0394\u0395\u0003\u00dcf\u0000\u0395\u0396\u0001"+ - "\u0000\u0000\u0000\u0396\u0397\u00069\u0011\u0000\u0397\u0083\u0001\u0000"+ - "\u0000\u0000\u0398\u0399\u0003\u00fau\u0000\u0399\u039a\u0001\u0000\u0000"+ - "\u0000\u039a\u039b\u0006:\u0018\u0000\u039b\u0085\u0001\u0000\u0000\u0000"+ - "\u039c\u039d\u0003\u01c0\u00d8\u0000\u039d\u039e\u0001\u0000\u0000\u0000"+ - "\u039e\u039f\u0006;\u001b\u0000\u039f\u0087\u0001\u0000\u0000\u0000\u03a0"+ - "\u03a1\u0003\u012c\u008e\u0000\u03a1\u03a2\u0001\u0000\u0000\u0000\u03a2"+ - "\u03a3\u0006<\u0013\u0000\u03a3\u0089\u0001\u0000\u0000\u0000\u03a4\u03a5"+ - "\u0003\u00f4r\u0000\u03a5\u03a6\u0001\u0000\u0000\u0000\u03a6\u03a7\u0006"+ - "=\u001c\u0000\u03a7\u008b\u0001\u0000\u0000\u0000\u03a8\u03a9\u0003\u011c"+ - "\u0086\u0000\u03a9\u03aa\u0001\u0000\u0000\u0000\u03aa\u03ab\u0006>\u001d"+ - "\u0000\u03ab\u008d\u0001\u0000\u0000\u0000\u03ac\u03ad\u0003\u0118\u0084"+ - "\u0000\u03ad\u03ae\u0001\u0000\u0000\u0000\u03ae\u03af\u0006?\u001e\u0000"+ - "\u03af\u008f\u0001\u0000\u0000\u0000\u03b0\u03b1\u0003\u011e\u0087\u0000"+ - "\u03b1\u03b2\u0001\u0000\u0000\u0000\u03b2\u03b3\u0006@\u001f\u0000\u03b3"+ - "\u0091\u0001\u0000\u0000\u0000\u03b4\u03b5\u0003\u0010\u0000\u0000\u03b5"+ - "\u03b6\u0001\u0000\u0000\u0000\u03b6\u03b7\u0006A\u0000\u0000\u03b7\u0093"+ - "\u0001\u0000\u0000\u0000\u03b8\u03b9\u0003\u0012\u0001\u0000\u03b9\u03ba"+ - "\u0001\u0000\u0000\u0000\u03ba\u03bb\u0006B\u0000\u0000\u03bb\u0095\u0001"+ - "\u0000\u0000\u0000\u03bc\u03bd\u0003\u0014\u0002\u0000\u03bd\u03be\u0001"+ - "\u0000\u0000\u0000\u03be\u03bf\u0006C\u0000\u0000\u03bf\u0097\u0001\u0000"+ - "\u0000\u0000\u03c0\u03c1\u0003\u0122\u0089\u0000\u03c1\u03c2\u0001\u0000"+ - "\u0000\u0000\u03c2\u03c3\u0006D \u0000\u03c3\u03c4\u0006D\u000e\u0000"+ - "\u03c4\u0099\u0001\u0000\u0000\u0000\u03c5\u03c6\u0003\u00d6c\u0000\u03c6"+ - "\u03c7\u0001\u0000\u0000\u0000\u03c7\u03c8\u0006E!\u0000\u03c8\u009b\u0001"+ - "\u0000\u0000\u0000\u03c9\u03cf\u0003\u00baU\u0000\u03ca\u03cf\u0003\u00b0"+ - "P\u0000\u03cb\u03cf\u0003\u00dcf\u0000\u03cc\u03cf\u0003\u00b2Q\u0000"+ - "\u03cd\u03cf\u0003\u00c0X\u0000\u03ce\u03c9\u0001\u0000\u0000\u0000\u03ce"+ - "\u03ca\u0001\u0000\u0000\u0000\u03ce\u03cb\u0001\u0000\u0000\u0000\u03ce"+ - "\u03cc\u0001\u0000\u0000\u0000\u03ce\u03cd\u0001\u0000\u0000\u0000\u03cf"+ - "\u03d0\u0001\u0000\u0000\u0000\u03d0\u03ce\u0001\u0000\u0000\u0000\u03d0"+ - "\u03d1\u0001\u0000\u0000\u0000\u03d1\u009d\u0001\u0000\u0000\u0000\u03d2"+ - "\u03d3\u0003\u0010\u0000\u0000\u03d3\u03d4\u0001\u0000\u0000\u0000\u03d4"+ - "\u03d5\u0006G\u0000\u0000\u03d5\u009f\u0001\u0000\u0000\u0000\u03d6\u03d7"+ - "\u0003\u0012\u0001\u0000\u03d7\u03d8\u0001\u0000\u0000\u0000\u03d8\u03d9"+ - "\u0006H\u0000\u0000\u03d9\u00a1\u0001\u0000\u0000\u0000\u03da\u03db\u0003"+ - "\u0014\u0002\u0000\u03db\u03dc\u0001\u0000\u0000\u0000\u03dc\u03dd\u0006"+ - "I\u0000\u0000\u03dd\u00a3\u0001\u0000\u0000\u0000\u03de\u03df\u0003\u0120"+ - "\u0088\u0000\u03df\u03e0\u0001\u0000\u0000\u0000\u03e0\u03e1\u0006J\u0015"+ - "\u0000\u03e1\u03e2\u0006J\"\u0000\u03e2\u00a5\u0001\u0000\u0000\u0000"+ - "\u03e3\u03e4\u0003\u00aeO\u0000\u03e4\u03e5\u0001\u0000\u0000\u0000\u03e5"+ - "\u03e6\u0006K\r\u0000\u03e6\u03e7\u0006K\u000e\u0000\u03e7\u00a7\u0001"+ - "\u0000\u0000\u0000\u03e8\u03e9\u0003\u0014\u0002\u0000\u03e9\u03ea\u0001"+ - "\u0000\u0000\u0000\u03ea\u03eb\u0006L\u0000\u0000\u03eb\u00a9\u0001\u0000"+ - "\u0000\u0000\u03ec\u03ed\u0003\u0010\u0000\u0000\u03ed\u03ee\u0001\u0000"+ - "\u0000\u0000\u03ee\u03ef\u0006M\u0000\u0000\u03ef\u00ab\u0001\u0000\u0000"+ - "\u0000\u03f0\u03f1\u0003\u0012\u0001\u0000\u03f1\u03f2\u0001\u0000\u0000"+ - "\u0000\u03f2\u03f3\u0006N\u0000\u0000\u03f3\u00ad\u0001\u0000\u0000\u0000"+ - "\u03f4\u03f5\u0005|\u0000\u0000\u03f5\u03f6\u0001\u0000\u0000\u0000\u03f6"+ - "\u03f7\u0006O\u000e\u0000\u03f7\u00af\u0001\u0000\u0000\u0000\u03f8\u03f9"+ - "\u0007\u0019\u0000\u0000\u03f9\u00b1\u0001\u0000\u0000\u0000\u03fa\u03fb"+ - "\u0007\u001a\u0000\u0000\u03fb\u00b3\u0001\u0000\u0000\u0000\u03fc\u03fd"+ - "\u0005\\\u0000\u0000\u03fd\u03fe\u0007\u001b\u0000\u0000\u03fe\u00b5\u0001"+ - "\u0000\u0000\u0000\u03ff\u0400\b\u001c\u0000\u0000\u0400\u00b7\u0001\u0000"+ - "\u0000\u0000\u0401\u0403\u0007\u0007\u0000\u0000\u0402\u0404\u0007\u001d"+ - "\u0000\u0000\u0403\u0402\u0001\u0000\u0000\u0000\u0403\u0404\u0001\u0000"+ - "\u0000\u0000\u0404\u0406\u0001\u0000\u0000\u0000\u0405\u0407\u0003\u00b0"+ - "P\u0000\u0406\u0405\u0001\u0000\u0000\u0000\u0407\u0408\u0001\u0000\u0000"+ - "\u0000\u0408\u0406\u0001\u0000\u0000\u0000\u0408\u0409\u0001\u0000\u0000"+ - "\u0000\u0409\u00b9\u0001\u0000\u0000\u0000\u040a\u040b\u0005@\u0000\u0000"+ - "\u040b\u00bb\u0001\u0000\u0000\u0000\u040c\u040d\u0005`\u0000\u0000\u040d"+ - "\u00bd\u0001\u0000\u0000\u0000\u040e\u0412\b\u001e\u0000\u0000\u040f\u0410"+ - "\u0005`\u0000\u0000\u0410\u0412\u0005`\u0000\u0000\u0411\u040e\u0001\u0000"+ - "\u0000\u0000\u0411\u040f\u0001\u0000\u0000\u0000\u0412\u00bf\u0001\u0000"+ - "\u0000\u0000\u0413\u0414\u0005_\u0000\u0000\u0414\u00c1\u0001\u0000\u0000"+ - "\u0000\u0415\u0419\u0003\u00b2Q\u0000\u0416\u0419\u0003\u00b0P\u0000\u0417"+ - "\u0419\u0003\u00c0X\u0000\u0418\u0415\u0001\u0000\u0000\u0000\u0418\u0416"+ - "\u0001\u0000\u0000\u0000\u0418\u0417\u0001\u0000\u0000\u0000\u0419\u00c3"+ - "\u0001\u0000\u0000\u0000\u041a\u041f\u0005\"\u0000\u0000\u041b\u041e\u0003"+ - "\u00b4R\u0000\u041c\u041e\u0003\u00b6S\u0000\u041d\u041b\u0001\u0000\u0000"+ - "\u0000\u041d\u041c\u0001\u0000\u0000\u0000\u041e\u0421\u0001\u0000\u0000"+ - "\u0000\u041f\u041d\u0001\u0000\u0000\u0000\u041f\u0420\u0001\u0000\u0000"+ - "\u0000\u0420\u0422\u0001\u0000\u0000\u0000\u0421\u041f\u0001\u0000\u0000"+ - "\u0000\u0422\u0438\u0005\"\u0000\u0000\u0423\u0424\u0005\"\u0000\u0000"+ - "\u0424\u0425\u0005\"\u0000\u0000\u0425\u0426\u0005\"\u0000\u0000\u0426"+ - "\u042a\u0001\u0000\u0000\u0000\u0427\u0429\b\u0000\u0000\u0000\u0428\u0427"+ - "\u0001\u0000\u0000\u0000\u0429\u042c\u0001\u0000\u0000\u0000\u042a\u042b"+ - "\u0001\u0000\u0000\u0000\u042a\u0428\u0001\u0000\u0000\u0000\u042b\u042d"+ - "\u0001\u0000\u0000\u0000\u042c\u042a\u0001\u0000\u0000\u0000\u042d\u042e"+ - "\u0005\"\u0000\u0000\u042e\u042f\u0005\"\u0000\u0000\u042f\u0430\u0005"+ - "\"\u0000\u0000\u0430\u0432\u0001\u0000\u0000\u0000\u0431\u0433\u0005\""+ - "\u0000\u0000\u0432\u0431\u0001\u0000\u0000\u0000\u0432\u0433\u0001\u0000"+ - "\u0000\u0000\u0433\u0435\u0001\u0000\u0000\u0000\u0434\u0436\u0005\"\u0000"+ - "\u0000\u0435\u0434\u0001\u0000\u0000\u0000\u0435\u0436\u0001\u0000\u0000"+ - "\u0000\u0436\u0438\u0001\u0000\u0000\u0000\u0437\u041a\u0001\u0000\u0000"+ - "\u0000\u0437\u0423\u0001\u0000\u0000\u0000\u0438\u00c5\u0001\u0000\u0000"+ - "\u0000\u0439\u043b\u0003\u00b0P\u0000\u043a\u0439\u0001\u0000\u0000\u0000"+ - "\u043b\u043c\u0001\u0000\u0000\u0000\u043c\u043a\u0001\u0000\u0000\u0000"+ - "\u043c\u043d\u0001\u0000\u0000\u0000\u043d\u00c7\u0001\u0000\u0000\u0000"+ - "\u043e\u0440\u0003\u00b0P\u0000\u043f\u043e\u0001\u0000\u0000\u0000\u0440"+ - "\u0441\u0001\u0000\u0000\u0000\u0441\u043f\u0001\u0000\u0000\u0000\u0441"+ - "\u0442\u0001\u0000\u0000\u0000\u0442\u0443\u0001\u0000\u0000\u0000\u0443"+ - "\u0447\u0003\u00dcf\u0000\u0444\u0446\u0003\u00b0P\u0000\u0445\u0444\u0001"+ - "\u0000\u0000\u0000\u0446\u0449\u0001\u0000\u0000\u0000\u0447\u0445\u0001"+ - "\u0000\u0000\u0000\u0447\u0448\u0001\u0000\u0000\u0000\u0448\u0469\u0001"+ - "\u0000\u0000\u0000\u0449\u0447\u0001\u0000\u0000\u0000\u044a\u044c\u0003"+ - "\u00dcf\u0000\u044b\u044d\u0003\u00b0P\u0000\u044c\u044b\u0001\u0000\u0000"+ - "\u0000\u044d\u044e\u0001\u0000\u0000\u0000\u044e\u044c\u0001\u0000\u0000"+ - "\u0000\u044e\u044f\u0001\u0000\u0000\u0000\u044f\u0469\u0001\u0000\u0000"+ - "\u0000\u0450\u0452\u0003\u00b0P\u0000\u0451\u0450\u0001\u0000\u0000\u0000"+ - "\u0452\u0453\u0001\u0000\u0000\u0000\u0453\u0451\u0001\u0000\u0000\u0000"+ - "\u0453\u0454\u0001\u0000\u0000\u0000\u0454\u045c\u0001\u0000\u0000\u0000"+ - "\u0455\u0459\u0003\u00dcf\u0000\u0456\u0458\u0003\u00b0P\u0000\u0457\u0456"+ - "\u0001\u0000\u0000\u0000\u0458\u045b\u0001\u0000\u0000\u0000\u0459\u0457"+ - "\u0001\u0000\u0000\u0000\u0459\u045a\u0001\u0000\u0000\u0000\u045a\u045d"+ - "\u0001\u0000\u0000\u0000\u045b\u0459\u0001\u0000\u0000\u0000\u045c\u0455"+ - "\u0001\u0000\u0000\u0000\u045c\u045d\u0001\u0000\u0000\u0000\u045d\u045e"+ - "\u0001\u0000\u0000\u0000\u045e\u045f\u0003\u00b8T\u0000\u045f\u0469\u0001"+ - "\u0000\u0000\u0000\u0460\u0462\u0003\u00dcf\u0000\u0461\u0463\u0003\u00b0"+ - "P\u0000\u0462\u0461\u0001\u0000\u0000\u0000\u0463\u0464\u0001\u0000\u0000"+ - "\u0000\u0464\u0462\u0001\u0000\u0000\u0000\u0464\u0465\u0001\u0000\u0000"+ - "\u0000\u0465\u0466\u0001\u0000\u0000\u0000\u0466\u0467\u0003\u00b8T\u0000"+ - "\u0467\u0469\u0001\u0000\u0000\u0000\u0468\u043f\u0001\u0000\u0000\u0000"+ - "\u0468\u044a\u0001\u0000\u0000\u0000\u0468\u0451\u0001\u0000\u0000\u0000"+ - "\u0468\u0460\u0001\u0000\u0000\u0000\u0469\u00c9\u0001\u0000\u0000\u0000"+ - "\u046a\u046b\u0007\u0004\u0000\u0000\u046b\u046c\u0007\u0005\u0000\u0000"+ - "\u046c\u046d\u0007\u0010\u0000\u0000\u046d\u00cb\u0001\u0000\u0000\u0000"+ - "\u046e\u046f\u0007\u0004\u0000\u0000\u046f\u0470\u0007\u0011\u0000\u0000"+ - "\u0470\u00cd\u0001\u0000\u0000\u0000\u0471\u0472\u0007\u0004\u0000\u0000"+ - "\u0472\u0473\u0007\u0011\u0000\u0000\u0473\u0474\u0007\u0002\u0000\u0000"+ - "\u0474\u00cf\u0001\u0000\u0000\u0000\u0475\u0476\u0005=\u0000\u0000\u0476"+ - "\u00d1\u0001\u0000\u0000\u0000\u0477\u0478\u0007\u001f\u0000\u0000\u0478"+ - "\u0479\u0007 \u0000\u0000\u0479\u00d3\u0001\u0000\u0000\u0000\u047a\u047b"+ - "\u0005:\u0000\u0000\u047b\u047c\u0005:\u0000\u0000\u047c\u00d5\u0001\u0000"+ - "\u0000\u0000\u047d\u047e\u0005:\u0000\u0000\u047e\u00d7\u0001\u0000\u0000"+ - "\u0000\u047f\u0480\u0005,\u0000\u0000\u0480\u00d9\u0001\u0000\u0000\u0000"+ - "\u0481\u0482\u0007\u0010\u0000\u0000\u0482\u0483\u0007\u0007\u0000\u0000"+ - "\u0483\u0484\u0007\u0011\u0000\u0000\u0484\u0485\u0007\u0002\u0000\u0000"+ - "\u0485\u00db\u0001\u0000\u0000\u0000\u0486\u0487\u0005.\u0000\u0000\u0487"+ - "\u00dd\u0001\u0000\u0000\u0000\u0488\u0489\u0007\u0015\u0000\u0000\u0489"+ - "\u048a\u0007\u0004\u0000\u0000\u048a\u048b\u0007\u000e\u0000\u0000\u048b"+ - "\u048c\u0007\u0011\u0000\u0000\u048c\u048d\u0007\u0007\u0000\u0000\u048d"+ - "\u00df\u0001\u0000\u0000\u0000\u048e\u048f\u0007\u0015\u0000\u0000\u048f"+ - "\u0490\u0007\n\u0000\u0000\u0490\u0491\u0007\f\u0000\u0000\u0491\u0492"+ - "\u0007\u0011\u0000\u0000\u0492\u0493\u0007\u000b\u0000\u0000\u0493\u00e1"+ - "\u0001\u0000\u0000\u0000\u0494\u0495\u0007\n\u0000\u0000\u0495\u0496\u0007"+ - "\u0005\u0000\u0000\u0496\u00e3\u0001\u0000\u0000\u0000\u0497\u0498\u0007"+ - "\n\u0000\u0000\u0498\u0499\u0007\u0011\u0000\u0000\u0499\u00e5\u0001\u0000"+ - "\u0000\u0000\u049a\u049b\u0007\u000e\u0000\u0000\u049b\u049c\u0007\u0004"+ - "\u0000\u0000\u049c\u049d\u0007\u0011\u0000\u0000\u049d\u049e\u0007\u000b"+ - "\u0000\u0000\u049e\u00e7\u0001\u0000\u0000\u0000\u049f\u04a0\u0007\u000e"+ - "\u0000\u0000\u04a0\u04a1\u0007\n\u0000\u0000\u04a1\u04a2\u0007\u0013\u0000"+ - "\u0000\u04a2\u04a3\u0007\u0007\u0000\u0000\u04a3\u00e9\u0001\u0000\u0000"+ - "\u0000\u04a4\u04a5\u0007\u0005\u0000\u0000\u04a5\u04a6\u0007\t\u0000\u0000"+ - "\u04a6\u04a7\u0007\u000b\u0000\u0000\u04a7\u00eb\u0001\u0000\u0000\u0000"+ - "\u04a8\u04a9\u0007\u0005\u0000\u0000\u04a9\u04aa\u0007\u0016\u0000\u0000"+ - "\u04aa\u04ab\u0007\u000e\u0000\u0000\u04ab\u04ac\u0007\u000e\u0000\u0000"+ - "\u04ac\u00ed\u0001\u0000\u0000\u0000\u04ad\u04ae\u0007\u0005\u0000\u0000"+ - "\u04ae\u04af\u0007\u0016\u0000\u0000\u04af\u04b0\u0007\u000e\u0000\u0000"+ - "\u04b0\u04b1\u0007\u000e\u0000\u0000\u04b1\u04b2\u0007\u0011\u0000\u0000"+ - "\u04b2\u00ef\u0001\u0000\u0000\u0000\u04b3\u04b4\u0007\t\u0000\u0000\u04b4"+ - "\u04b5\u0007\u0005\u0000\u0000\u04b5\u00f1\u0001\u0000\u0000\u0000\u04b6"+ - "\u04b7\u0007\t\u0000\u0000\u04b7\u04b8\u0007\f\u0000\u0000\u04b8\u00f3"+ - "\u0001\u0000\u0000\u0000\u04b9\u04ba\u0005?\u0000\u0000\u04ba\u00f5\u0001"+ - "\u0000\u0000\u0000\u04bb\u04bc\u0007\f\u0000\u0000\u04bc\u04bd\u0007\u000e"+ - "\u0000\u0000\u04bd\u04be\u0007\n\u0000\u0000\u04be\u04bf\u0007\u0013\u0000"+ - "\u0000\u04bf\u04c0\u0007\u0007\u0000\u0000\u04c0\u00f7\u0001\u0000\u0000"+ - "\u0000\u04c1\u04c2\u0007\u000b\u0000\u0000\u04c2\u04c3\u0007\f\u0000\u0000"+ - "\u04c3\u04c4\u0007\u0016\u0000\u0000\u04c4\u04c5\u0007\u0007\u0000\u0000"+ - "\u04c5\u00f9\u0001\u0000\u0000\u0000\u04c6\u04c7\u0007\u0014\u0000\u0000"+ - "\u04c7\u04c8\u0007\n\u0000\u0000\u04c8\u04c9\u0007\u000b\u0000\u0000\u04c9"+ - "\u04ca\u0007\u0003\u0000\u0000\u04ca\u00fb\u0001\u0000\u0000\u0000\u04cb"+ - "\u04cc\u0005=\u0000\u0000\u04cc\u04cd\u0005=\u0000\u0000\u04cd\u00fd\u0001"+ - "\u0000\u0000\u0000\u04ce\u04cf\u0005=\u0000\u0000\u04cf\u04d0\u0005~\u0000"+ - "\u0000\u04d0\u00ff\u0001\u0000\u0000\u0000\u04d1\u04d2\u0005!\u0000\u0000"+ - "\u04d2\u04d3\u0005=\u0000\u0000\u04d3\u0101\u0001\u0000\u0000\u0000\u04d4"+ - "\u04d5\u0005<\u0000\u0000\u04d5\u0103\u0001\u0000\u0000\u0000\u04d6\u04d7"+ - "\u0005<\u0000\u0000\u04d7\u04d8\u0005=\u0000\u0000\u04d8\u0105\u0001\u0000"+ - "\u0000\u0000\u04d9\u04da\u0005>\u0000\u0000\u04da\u0107\u0001\u0000\u0000"+ - "\u0000\u04db\u04dc\u0005>\u0000\u0000\u04dc\u04dd\u0005=\u0000\u0000\u04dd"+ - "\u0109\u0001\u0000\u0000\u0000\u04de\u04df\u0005+\u0000\u0000\u04df\u010b"+ - "\u0001\u0000\u0000\u0000\u04e0\u04e1\u0005-\u0000\u0000\u04e1\u010d\u0001"+ - "\u0000\u0000\u0000\u04e2\u04e3\u0005*\u0000\u0000\u04e3\u010f\u0001\u0000"+ - "\u0000\u0000\u04e4\u04e5\u0005/\u0000\u0000\u04e5\u0111\u0001\u0000\u0000"+ - "\u0000\u04e6\u04e7\u0005%\u0000\u0000\u04e7\u0113\u0001\u0000\u0000\u0000"+ - "\u04e8\u04e9\u0005{\u0000\u0000\u04e9\u0115\u0001\u0000\u0000\u0000\u04ea"+ - "\u04eb\u0005}\u0000\u0000\u04eb\u0117\u0001\u0000\u0000\u0000\u04ec\u04ed"+ - "\u0005?\u0000\u0000\u04ed\u04ee\u0005?\u0000\u0000\u04ee\u0119\u0001\u0000"+ - "\u0000\u0000\u04ef\u04f0\u0003,\u000e\u0000\u04f0\u04f1\u0001\u0000\u0000"+ - "\u0000\u04f1\u04f2\u0006\u0085#\u0000\u04f2\u011b\u0001\u0000\u0000\u0000"+ - "\u04f3\u04f6\u0003\u00f4r\u0000\u04f4\u04f7\u0003\u00b2Q\u0000\u04f5\u04f7"+ - "\u0003\u00c0X\u0000\u04f6\u04f4\u0001\u0000\u0000\u0000\u04f6\u04f5\u0001"+ - "\u0000\u0000\u0000\u04f7\u04fb\u0001\u0000\u0000\u0000\u04f8\u04fa\u0003"+ - "\u00c2Y\u0000\u04f9\u04f8\u0001\u0000\u0000\u0000\u04fa\u04fd\u0001\u0000"+ - "\u0000\u0000\u04fb\u04f9\u0001\u0000\u0000\u0000\u04fb\u04fc\u0001\u0000"+ - "\u0000\u0000\u04fc\u0505\u0001\u0000\u0000\u0000\u04fd\u04fb\u0001\u0000"+ - "\u0000\u0000\u04fe\u0500\u0003\u00f4r\u0000\u04ff\u0501\u0003\u00b0P\u0000"+ - "\u0500\u04ff\u0001\u0000\u0000\u0000\u0501\u0502\u0001\u0000\u0000\u0000"+ - "\u0502\u0500\u0001\u0000\u0000\u0000\u0502\u0503\u0001\u0000\u0000\u0000"+ - "\u0503\u0505\u0001\u0000\u0000\u0000\u0504\u04f3\u0001\u0000\u0000\u0000"+ - "\u0504\u04fe\u0001\u0000\u0000\u0000\u0505\u011d\u0001\u0000\u0000\u0000"+ - "\u0506\u0509\u0003\u0118\u0084\u0000\u0507\u050a\u0003\u00b2Q\u0000\u0508"+ - "\u050a\u0003\u00c0X\u0000\u0509\u0507\u0001\u0000\u0000\u0000\u0509\u0508"+ - "\u0001\u0000\u0000\u0000\u050a\u050e\u0001\u0000\u0000\u0000\u050b\u050d"+ - "\u0003\u00c2Y\u0000\u050c\u050b\u0001\u0000\u0000\u0000\u050d\u0510\u0001"+ - "\u0000\u0000\u0000\u050e\u050c\u0001\u0000\u0000\u0000\u050e\u050f\u0001"+ - "\u0000\u0000\u0000\u050f\u0518\u0001\u0000\u0000\u0000\u0510\u050e\u0001"+ - "\u0000\u0000\u0000\u0511\u0513\u0003\u0118\u0084\u0000\u0512\u0514\u0003"+ - "\u00b0P\u0000\u0513\u0512\u0001\u0000\u0000\u0000\u0514\u0515\u0001\u0000"+ - "\u0000\u0000\u0515\u0513\u0001\u0000\u0000\u0000\u0515\u0516\u0001\u0000"+ - "\u0000\u0000\u0516\u0518\u0001\u0000\u0000\u0000\u0517\u0506\u0001\u0000"+ - "\u0000\u0000\u0517\u0511\u0001\u0000\u0000\u0000\u0518\u011f\u0001\u0000"+ - "\u0000\u0000\u0519\u051a\u0005[\u0000\u0000\u051a\u051b\u0001\u0000\u0000"+ - "\u0000\u051b\u051c\u0006\u0088\u0004\u0000\u051c\u051d\u0006\u0088\u0004"+ - "\u0000\u051d\u0121\u0001\u0000\u0000\u0000\u051e\u051f\u0005]\u0000\u0000"+ - "\u051f\u0520\u0001\u0000\u0000\u0000\u0520\u0521\u0006\u0089\u000e\u0000"+ - "\u0521\u0522\u0006\u0089\u000e\u0000\u0522\u0123\u0001\u0000\u0000\u0000"+ - "\u0523\u0524\u0005(\u0000\u0000\u0524\u0525\u0001\u0000\u0000\u0000\u0525"+ - "\u0526\u0006\u008a\u0004\u0000\u0526\u0527\u0006\u008a\u0004\u0000\u0527"+ - "\u0125\u0001\u0000\u0000\u0000\u0528\u0529\u0005)\u0000\u0000\u0529\u052a"+ - "\u0001\u0000\u0000\u0000\u052a\u052b\u0006\u008b\u000e\u0000\u052b\u052c"+ - "\u0006\u008b\u000e\u0000\u052c\u0127\u0001\u0000\u0000\u0000\u052d\u0531"+ - "\u0003\u00b2Q\u0000\u052e\u0530\u0003\u00c2Y\u0000\u052f\u052e\u0001\u0000"+ - "\u0000\u0000\u0530\u0533\u0001\u0000\u0000\u0000\u0531\u052f\u0001\u0000"+ - "\u0000\u0000\u0531\u0532\u0001\u0000\u0000\u0000\u0532\u053e\u0001\u0000"+ - "\u0000\u0000\u0533\u0531\u0001\u0000\u0000\u0000\u0534\u0537\u0003\u00c0"+ - "X\u0000\u0535\u0537\u0003\u00baU\u0000\u0536\u0534\u0001\u0000\u0000\u0000"+ - "\u0536\u0535\u0001\u0000\u0000\u0000\u0537\u0539\u0001\u0000\u0000\u0000"+ - "\u0538\u053a\u0003\u00c2Y\u0000\u0539\u0538\u0001\u0000\u0000\u0000\u053a"+ - "\u053b\u0001\u0000\u0000\u0000\u053b\u0539\u0001\u0000\u0000\u0000\u053b"+ - "\u053c\u0001\u0000\u0000\u0000\u053c\u053e\u0001\u0000\u0000\u0000\u053d"+ - "\u052d\u0001\u0000\u0000\u0000\u053d\u0536\u0001\u0000\u0000\u0000\u053e"+ - "\u0129\u0001\u0000\u0000\u0000\u053f\u0541\u0003\u00bcV\u0000\u0540\u0542"+ - "\u0003\u00beW\u0000\u0541\u0540\u0001\u0000\u0000\u0000\u0542\u0543\u0001"+ - "\u0000\u0000\u0000\u0543\u0541\u0001\u0000\u0000\u0000\u0543\u0544\u0001"+ - "\u0000\u0000\u0000\u0544\u0545\u0001\u0000\u0000\u0000\u0545\u0546\u0003"+ - "\u00bcV\u0000\u0546\u012b\u0001\u0000\u0000\u0000\u0547\u0548\u0003\u012a"+ - "\u008d\u0000\u0548\u012d\u0001\u0000\u0000\u0000\u0549\u054a\u0003\u0010"+ - "\u0000\u0000\u054a\u054b\u0001\u0000\u0000\u0000\u054b\u054c\u0006\u008f"+ - "\u0000\u0000\u054c\u012f\u0001\u0000\u0000\u0000\u054d\u054e\u0003\u0012"+ - "\u0001\u0000\u054e\u054f\u0001\u0000\u0000\u0000\u054f\u0550\u0006\u0090"+ - "\u0000\u0000\u0550\u0131\u0001\u0000\u0000\u0000\u0551\u0552\u0003\u0014"+ - "\u0002\u0000\u0552\u0553\u0001\u0000\u0000\u0000\u0553\u0554\u0006\u0091"+ - "\u0000\u0000\u0554\u0133\u0001\u0000\u0000\u0000\u0555\u0556\u0003\u00ae"+ - "O\u0000\u0556\u0557\u0001\u0000\u0000\u0000\u0557\u0558\u0006\u0092\r"+ - "\u0000\u0558\u0559\u0006\u0092\u000e\u0000\u0559\u0135\u0001\u0000\u0000"+ - "\u0000\u055a\u055b\u0003\u0120\u0088\u0000\u055b\u055c\u0001\u0000\u0000"+ - "\u0000\u055c\u055d\u0006\u0093\u0015\u0000\u055d\u0137\u0001\u0000\u0000"+ - "\u0000\u055e\u055f\u0003\u0122\u0089\u0000\u055f\u0560\u0001\u0000\u0000"+ - "\u0000\u0560\u0561\u0006\u0094 \u0000\u0561\u0139\u0001\u0000\u0000\u0000"+ - "\u0562\u0563\u0003\u00d6c\u0000\u0563\u0564\u0001\u0000\u0000\u0000\u0564"+ - "\u0565\u0006\u0095!\u0000\u0565\u013b\u0001\u0000\u0000\u0000\u0566\u0567"+ - "\u0003\u00d4b\u0000\u0567\u0568\u0001\u0000\u0000\u0000\u0568\u0569\u0006"+ - "\u0096$\u0000\u0569\u013d\u0001\u0000\u0000\u0000\u056a\u056b\u0003\u00d8"+ - "d\u0000\u056b\u056c\u0001\u0000\u0000\u0000\u056c\u056d\u0006\u0097\u0012"+ - "\u0000\u056d\u013f\u0001\u0000\u0000\u0000\u056e\u056f\u0003\u00d0`\u0000"+ - "\u056f\u0570\u0001\u0000\u0000\u0000\u0570\u0571\u0006\u0098\u001a\u0000"+ - "\u0571\u0141\u0001\u0000\u0000\u0000\u0572\u0573\u0007\u000f\u0000\u0000"+ - "\u0573\u0574\u0007\u0007\u0000\u0000\u0574\u0575\u0007\u000b\u0000\u0000"+ - "\u0575\u0576\u0007\u0004\u0000\u0000\u0576\u0577\u0007\u0010\u0000\u0000"+ - "\u0577\u0578\u0007\u0004\u0000\u0000\u0578\u0579\u0007\u000b\u0000\u0000"+ - "\u0579\u057a\u0007\u0004\u0000\u0000\u057a\u0143\u0001\u0000\u0000\u0000"+ - "\u057b\u057f\b!\u0000\u0000\u057c\u057d\u0005/\u0000\u0000\u057d\u057f"+ - "\b\"\u0000\u0000\u057e\u057b\u0001\u0000\u0000\u0000\u057e\u057c\u0001"+ - "\u0000\u0000\u0000\u057f\u0145\u0001\u0000\u0000\u0000\u0580\u0582\u0003"+ - "\u0144\u009a\u0000\u0581\u0580\u0001\u0000\u0000\u0000\u0582\u0583\u0001"+ - "\u0000\u0000\u0000\u0583\u0581\u0001\u0000\u0000\u0000\u0583\u0584\u0001"+ - "\u0000\u0000\u0000\u0584\u0147\u0001\u0000\u0000\u0000\u0585\u0586\u0003"+ - "\u0146\u009b\u0000\u0586\u0587\u0001\u0000\u0000\u0000\u0587\u0588\u0006"+ - "\u009c%\u0000\u0588\u0149\u0001\u0000\u0000\u0000\u0589\u058a\u0003\u00c4"+ - "Z\u0000\u058a\u058b\u0001\u0000\u0000\u0000\u058b\u058c\u0006\u009d&\u0000"+ - "\u058c\u014b\u0001\u0000\u0000\u0000\u058d\u058e\u0003\u0010\u0000\u0000"+ - "\u058e\u058f\u0001\u0000\u0000\u0000\u058f\u0590\u0006\u009e\u0000\u0000"+ - "\u0590\u014d\u0001\u0000\u0000\u0000\u0591\u0592\u0003\u0012\u0001\u0000"+ - "\u0592\u0593\u0001\u0000\u0000\u0000\u0593\u0594\u0006\u009f\u0000\u0000"+ - "\u0594\u014f\u0001\u0000\u0000\u0000\u0595\u0596\u0003\u0014\u0002\u0000"+ - "\u0596\u0597\u0001\u0000\u0000\u0000\u0597\u0598\u0006\u00a0\u0000\u0000"+ - "\u0598\u0151\u0001\u0000\u0000\u0000\u0599\u059a\u0003\u0124\u008a\u0000"+ - "\u059a\u059b\u0001\u0000\u0000\u0000\u059b\u059c\u0006\u00a1\'\u0000\u059c"+ - "\u059d\u0006\u00a1\"\u0000\u059d\u0153\u0001\u0000\u0000\u0000\u059e\u059f"+ - "\u0003\u00aeO\u0000\u059f\u05a0\u0001\u0000\u0000\u0000\u05a0\u05a1\u0006"+ - "\u00a2\r\u0000\u05a1\u05a2\u0006\u00a2\u000e\u0000\u05a2\u0155\u0001\u0000"+ - "\u0000\u0000\u05a3\u05a4\u0003\u0014\u0002\u0000\u05a4\u05a5\u0001\u0000"+ - "\u0000\u0000\u05a5\u05a6\u0006\u00a3\u0000\u0000\u05a6\u0157\u0001\u0000"+ - "\u0000\u0000\u05a7\u05a8\u0003\u0010\u0000\u0000\u05a8\u05a9\u0001\u0000"+ - "\u0000\u0000\u05a9\u05aa\u0006\u00a4\u0000\u0000\u05aa\u0159\u0001\u0000"+ - "\u0000\u0000\u05ab\u05ac\u0003\u0012\u0001\u0000\u05ac\u05ad\u0001\u0000"+ - "\u0000\u0000\u05ad\u05ae\u0006\u00a5\u0000\u0000\u05ae\u015b\u0001\u0000"+ - "\u0000\u0000\u05af\u05b0\u0003\u00aeO\u0000\u05b0\u05b1\u0001\u0000\u0000"+ - "\u0000\u05b1\u05b2\u0006\u00a6\r\u0000\u05b2\u05b3\u0006\u00a6\u000e\u0000"+ - "\u05b3\u015d\u0001\u0000\u0000\u0000\u05b4\u05b5\u0007#\u0000\u0000\u05b5"+ - "\u05b6\u0007\t\u0000\u0000\u05b6\u05b7\u0007\n\u0000\u0000\u05b7\u05b8"+ - "\u0007\u0005\u0000\u0000\u05b8\u015f\u0001\u0000\u0000\u0000\u05b9\u05ba"+ - "\u0003\u00cc^\u0000\u05ba\u05bb\u0001\u0000\u0000\u0000\u05bb\u05bc\u0006"+ - "\u00a8\u0010\u0000\u05bc\u0161\u0001\u0000\u0000\u0000\u05bd\u05be\u0003"+ - "\u00f0p\u0000\u05be\u05bf\u0001\u0000\u0000\u0000\u05bf\u05c0\u0006\u00a9"+ - "\u000f\u0000\u05c0\u05c1\u0006\u00a9\u000e\u0000\u05c1\u05c2\u0006\u00a9"+ - "\u0004\u0000\u05c2\u0163\u0001\u0000\u0000\u0000\u05c3\u05c4\u0007\u0016"+ - "\u0000\u0000\u05c4\u05c5\u0007\u0011\u0000\u0000\u05c5\u05c6\u0007\n\u0000"+ - "\u0000\u05c6\u05c7\u0007\u0005\u0000\u0000\u05c7\u05c8\u0007\u0006\u0000"+ - "\u0000\u05c8\u05c9\u0001\u0000\u0000\u0000\u05c9\u05ca\u0006\u00aa\u000e"+ - "\u0000\u05ca\u05cb\u0006\u00aa\u0004\u0000\u05cb\u0165\u0001\u0000\u0000"+ - "\u0000\u05cc\u05cd\u0003\u0146\u009b\u0000\u05cd\u05ce\u0001\u0000\u0000"+ - "\u0000\u05ce\u05cf\u0006\u00ab%\u0000\u05cf\u0167\u0001\u0000\u0000\u0000"+ - "\u05d0\u05d1\u0003\u00c4Z\u0000\u05d1\u05d2\u0001\u0000\u0000\u0000\u05d2"+ - "\u05d3\u0006\u00ac&\u0000\u05d3\u0169\u0001\u0000\u0000\u0000\u05d4\u05d5"+ - "\u0003\u00d6c\u0000\u05d5\u05d6\u0001\u0000\u0000\u0000\u05d6\u05d7\u0006"+ - "\u00ad!\u0000\u05d7\u016b\u0001\u0000\u0000\u0000\u05d8\u05d9\u0003\u0128"+ - "\u008c\u0000\u05d9\u05da\u0001\u0000\u0000\u0000\u05da\u05db\u0006\u00ae"+ - "\u0014\u0000\u05db\u016d\u0001\u0000\u0000\u0000\u05dc\u05dd\u0003\u012c"+ - "\u008e\u0000\u05dd\u05de\u0001\u0000\u0000\u0000\u05de\u05df\u0006\u00af"+ - "\u0013\u0000\u05df\u016f\u0001\u0000\u0000\u0000\u05e0\u05e1\u0003\u0010"+ - "\u0000\u0000\u05e1\u05e2\u0001\u0000\u0000\u0000\u05e2\u05e3\u0006\u00b0"+ - "\u0000\u0000\u05e3\u0171\u0001\u0000\u0000\u0000\u05e4\u05e5\u0003\u0012"+ - "\u0001\u0000\u05e5\u05e6\u0001\u0000\u0000\u0000\u05e6\u05e7\u0006\u00b1"+ - "\u0000\u0000\u05e7\u0173\u0001\u0000\u0000\u0000\u05e8\u05e9\u0003\u0014"+ - "\u0002\u0000\u05e9\u05ea\u0001\u0000\u0000\u0000\u05ea\u05eb\u0006\u00b2"+ - "\u0000\u0000\u05eb\u0175\u0001\u0000\u0000\u0000\u05ec\u05ed\u0003\u00ae"+ - "O\u0000\u05ed\u05ee\u0001\u0000\u0000\u0000\u05ee\u05ef\u0006\u00b3\r"+ - "\u0000\u05ef\u05f0\u0006\u00b3\u000e\u0000\u05f0\u0177\u0001\u0000\u0000"+ - "\u0000\u05f1\u05f2\u0003\u00d6c\u0000\u05f2\u05f3\u0001\u0000\u0000\u0000"+ - "\u05f3\u05f4\u0006\u00b4!\u0000\u05f4\u0179\u0001\u0000\u0000\u0000\u05f5"+ - "\u05f6\u0003\u00d8d\u0000\u05f6\u05f7\u0001\u0000\u0000\u0000\u05f7\u05f8"+ - "\u0006\u00b5\u0012\u0000\u05f8\u017b\u0001\u0000\u0000\u0000\u05f9\u05fa"+ - "\u0003\u00dcf\u0000\u05fa\u05fb\u0001\u0000\u0000\u0000\u05fb\u05fc\u0006"+ - "\u00b6\u0011\u0000\u05fc\u017d\u0001\u0000\u0000\u0000\u05fd\u05fe\u0003"+ - "\u00f0p\u0000\u05fe\u05ff\u0001\u0000\u0000\u0000\u05ff\u0600\u0006\u00b7"+ - "\u000f\u0000\u0600\u0601\u0006\u00b7(\u0000\u0601\u017f\u0001\u0000\u0000"+ - "\u0000\u0602\u0603\u0003\u0146\u009b\u0000\u0603\u0604\u0001\u0000\u0000"+ - "\u0000\u0604\u0605\u0006\u00b8%\u0000\u0605\u0181\u0001\u0000\u0000\u0000"+ - "\u0606\u0607\u0003\u00c4Z\u0000\u0607\u0608\u0001\u0000\u0000\u0000\u0608"+ - "\u0609\u0006\u00b9&\u0000\u0609\u0183\u0001\u0000\u0000\u0000\u060a\u060b"+ - "\u0003\u0010\u0000\u0000\u060b\u060c\u0001\u0000\u0000\u0000\u060c\u060d"+ - "\u0006\u00ba\u0000\u0000\u060d\u0185\u0001\u0000\u0000\u0000\u060e\u060f"+ - "\u0003\u0012\u0001\u0000\u060f\u0610\u0001\u0000\u0000\u0000\u0610\u0611"+ - "\u0006\u00bb\u0000\u0000\u0611\u0187\u0001\u0000\u0000\u0000\u0612\u0613"+ - "\u0003\u0014\u0002\u0000\u0613\u0614\u0001\u0000\u0000\u0000\u0614\u0615"+ - "\u0006\u00bc\u0000\u0000\u0615\u0189\u0001\u0000\u0000\u0000\u0616\u0617"+ - "\u0003\u00aeO\u0000\u0617\u0618\u0001\u0000\u0000\u0000\u0618\u0619\u0006"+ - "\u00bd\r\u0000\u0619\u061a\u0006\u00bd\u000e\u0000\u061a\u061b\u0006\u00bd"+ - "\u000e\u0000\u061b\u018b\u0001\u0000\u0000\u0000\u061c\u061d\u0003\u00d8"+ - "d\u0000\u061d\u061e\u0001\u0000\u0000\u0000\u061e\u061f\u0006\u00be\u0012"+ - "\u0000\u061f\u018d\u0001\u0000\u0000\u0000\u0620\u0621\u0003\u00dcf\u0000"+ - "\u0621\u0622\u0001\u0000\u0000\u0000\u0622\u0623\u0006\u00bf\u0011\u0000"+ - "\u0623\u018f\u0001\u0000\u0000\u0000\u0624\u0625\u0003\u01c0\u00d8\u0000"+ - "\u0625\u0626\u0001\u0000\u0000\u0000\u0626\u0627\u0006\u00c0\u001b\u0000"+ - "\u0627\u0191\u0001\u0000\u0000\u0000\u0628\u0629\u0003\u0010\u0000\u0000"+ - "\u0629\u062a\u0001\u0000\u0000\u0000\u062a\u062b\u0006\u00c1\u0000\u0000"+ - "\u062b\u0193\u0001\u0000\u0000\u0000\u062c\u062d\u0003\u0012\u0001\u0000"+ - "\u062d\u062e\u0001\u0000\u0000\u0000\u062e\u062f\u0006\u00c2\u0000\u0000"+ - "\u062f\u0195\u0001\u0000\u0000\u0000\u0630\u0631\u0003\u0014\u0002\u0000"+ - "\u0631\u0632\u0001\u0000\u0000\u0000\u0632\u0633\u0006\u00c3\u0000\u0000"+ - "\u0633\u0197\u0001\u0000\u0000\u0000\u0634\u0635\u0003\u00aeO\u0000\u0635"+ - "\u0636\u0001\u0000\u0000\u0000\u0636\u0637\u0006\u00c4\r\u0000\u0637\u0638"+ - "\u0006\u00c4\u000e\u0000\u0638\u0199\u0001\u0000\u0000\u0000\u0639\u063a"+ - "\u0003\u00dcf\u0000\u063a\u063b\u0001\u0000\u0000\u0000\u063b\u063c\u0006"+ - "\u00c5\u0011\u0000\u063c\u019b\u0001\u0000\u0000\u0000\u063d\u063e\u0003"+ - "\u00f4r\u0000\u063e\u063f\u0001\u0000\u0000\u0000\u063f\u0640\u0006\u00c6"+ - "\u001c\u0000\u0640\u019d\u0001\u0000\u0000\u0000\u0641\u0642\u0003\u011c"+ - "\u0086\u0000\u0642\u0643\u0001\u0000\u0000\u0000\u0643\u0644\u0006\u00c7"+ - "\u001d\u0000\u0644\u019f\u0001\u0000\u0000\u0000\u0645\u0646\u0003\u0118"+ - "\u0084\u0000\u0646\u0647\u0001\u0000\u0000\u0000\u0647\u0648\u0006\u00c8"+ - "\u001e\u0000\u0648\u01a1\u0001\u0000\u0000\u0000\u0649\u064a\u0003\u011e"+ - "\u0087\u0000\u064a\u064b\u0001\u0000\u0000\u0000\u064b\u064c\u0006\u00c9"+ - "\u001f\u0000\u064c\u01a3\u0001\u0000\u0000\u0000\u064d\u064e\u0003\u012c"+ - "\u008e\u0000\u064e\u064f\u0001\u0000\u0000\u0000\u064f\u0650\u0006\u00ca"+ - "\u0013\u0000\u0650\u01a5\u0001\u0000\u0000\u0000\u0651\u0652\u0003\u0128"+ - "\u008c\u0000\u0652\u0653\u0001\u0000\u0000\u0000\u0653\u0654\u0006\u00cb"+ - "\u0014\u0000\u0654\u01a7\u0001\u0000\u0000\u0000\u0655\u0656\u0003\u0010"+ - "\u0000\u0000\u0656\u0657\u0001\u0000\u0000\u0000\u0657\u0658\u0006\u00cc"+ - "\u0000\u0000\u0658\u01a9\u0001\u0000\u0000\u0000\u0659\u065a\u0003\u0012"+ - "\u0001\u0000\u065a\u065b\u0001\u0000\u0000\u0000\u065b\u065c\u0006\u00cd"+ - "\u0000\u0000\u065c\u01ab\u0001\u0000\u0000\u0000\u065d\u065e\u0003\u0014"+ - "\u0002\u0000\u065e\u065f\u0001\u0000\u0000\u0000\u065f\u0660\u0006\u00ce"+ - "\u0000\u0000\u0660\u01ad\u0001\u0000\u0000\u0000\u0661\u0662\u0003\u00ae"+ - "O\u0000\u0662\u0663\u0001\u0000\u0000\u0000\u0663\u0664\u0006\u00cf\r"+ - "\u0000\u0664\u0665\u0006\u00cf\u000e\u0000\u0665\u01af\u0001\u0000\u0000"+ - "\u0000\u0666\u0667\u0003\u00dcf\u0000\u0667\u0668\u0001\u0000\u0000\u0000"+ - "\u0668\u0669\u0006\u00d0\u0011\u0000\u0669\u01b1\u0001\u0000\u0000\u0000"+ - "\u066a\u066b\u0003\u00d8d\u0000\u066b\u066c\u0001\u0000\u0000\u0000\u066c"+ - "\u066d\u0006\u00d1\u0012\u0000\u066d\u01b3\u0001\u0000\u0000\u0000\u066e"+ - "\u066f\u0003\u00f4r\u0000\u066f\u0670\u0001\u0000\u0000\u0000\u0670\u0671"+ - "\u0006\u00d2\u001c\u0000\u0671\u01b5\u0001\u0000\u0000\u0000\u0672\u0673"+ - "\u0003\u011c\u0086\u0000\u0673\u0674\u0001\u0000\u0000\u0000\u0674\u0675"+ - "\u0006\u00d3\u001d\u0000\u0675\u01b7\u0001\u0000\u0000\u0000\u0676\u0677"+ - "\u0003\u0118\u0084\u0000\u0677\u0678\u0001\u0000\u0000\u0000\u0678\u0679"+ - "\u0006\u00d4\u001e\u0000\u0679\u01b9\u0001\u0000\u0000\u0000\u067a\u067b"+ - "\u0003\u011e\u0087\u0000\u067b\u067c\u0001\u0000\u0000\u0000\u067c\u067d"+ - "\u0006\u00d5\u001f\u0000\u067d\u01bb\u0001\u0000\u0000\u0000\u067e\u0683"+ - "\u0003\u00b2Q\u0000\u067f\u0683\u0003\u00b0P\u0000\u0680\u0683\u0003\u00c0"+ - "X\u0000\u0681\u0683\u0003\u010e\u007f\u0000\u0682\u067e\u0001\u0000\u0000"+ - "\u0000\u0682\u067f\u0001\u0000\u0000\u0000\u0682\u0680\u0001\u0000\u0000"+ - "\u0000\u0682\u0681\u0001\u0000\u0000\u0000\u0683\u01bd\u0001\u0000\u0000"+ - "\u0000\u0684\u0687\u0003\u00b2Q\u0000\u0685\u0687\u0003\u010e\u007f\u0000"+ - "\u0686\u0684\u0001\u0000\u0000\u0000\u0686\u0685\u0001\u0000\u0000\u0000"+ - "\u0687\u068b\u0001\u0000\u0000\u0000\u0688\u068a\u0003\u01bc\u00d6\u0000"+ - "\u0689\u0688\u0001\u0000\u0000\u0000\u068a\u068d\u0001\u0000\u0000\u0000"+ - "\u068b\u0689\u0001\u0000\u0000\u0000\u068b\u068c\u0001\u0000\u0000\u0000"+ - "\u068c\u0698\u0001\u0000\u0000\u0000\u068d\u068b\u0001\u0000\u0000\u0000"+ - "\u068e\u0691\u0003\u00c0X\u0000\u068f\u0691\u0003\u00baU\u0000\u0690\u068e"+ - "\u0001\u0000\u0000\u0000\u0690\u068f\u0001\u0000\u0000\u0000\u0691\u0693"+ - "\u0001\u0000\u0000\u0000\u0692\u0694\u0003\u01bc\u00d6\u0000\u0693\u0692"+ - "\u0001\u0000\u0000\u0000\u0694\u0695\u0001\u0000\u0000\u0000\u0695\u0693"+ - "\u0001\u0000\u0000\u0000\u0695\u0696\u0001\u0000\u0000\u0000\u0696\u0698"+ - "\u0001\u0000\u0000\u0000\u0697\u0686\u0001\u0000\u0000\u0000\u0697\u0690"+ - "\u0001\u0000\u0000\u0000\u0698\u01bf\u0001\u0000\u0000\u0000\u0699\u069c"+ - "\u0003\u01be\u00d7\u0000\u069a\u069c\u0003\u012a\u008d\u0000\u069b\u0699"+ - "\u0001\u0000\u0000\u0000\u069b\u069a\u0001\u0000\u0000\u0000\u069c\u069d"+ - "\u0001\u0000\u0000\u0000\u069d\u069b\u0001\u0000\u0000\u0000\u069d\u069e"+ - "\u0001\u0000\u0000\u0000\u069e\u01c1\u0001\u0000\u0000\u0000\u069f\u06a0"+ - "\u0003\u0010\u0000\u0000\u06a0\u06a1\u0001\u0000\u0000\u0000\u06a1\u06a2"+ - "\u0006\u00d9\u0000\u0000\u06a2\u01c3\u0001\u0000\u0000\u0000\u06a3\u06a4"+ - "\u0003\u0012\u0001\u0000\u06a4\u06a5\u0001\u0000\u0000\u0000\u06a5\u06a6"+ - "\u0006\u00da\u0000\u0000\u06a6\u01c5\u0001\u0000\u0000\u0000\u06a7\u06a8"+ - "\u0003\u0014\u0002\u0000\u06a8\u06a9\u0001\u0000\u0000\u0000\u06a9\u06aa"+ - "\u0006\u00db\u0000\u0000\u06aa\u01c7\u0001\u0000\u0000\u0000\u06ab\u06ac"+ - "\u0003\u00aeO\u0000\u06ac\u06ad\u0001\u0000\u0000\u0000\u06ad\u06ae\u0006"+ - "\u00dc\r\u0000\u06ae\u06af\u0006\u00dc\u000e\u0000\u06af\u01c9\u0001\u0000"+ - "\u0000\u0000\u06b0\u06b1\u0003\u00d0`\u0000\u06b1\u06b2\u0001\u0000\u0000"+ - "\u0000\u06b2\u06b3\u0006\u00dd\u001a\u0000\u06b3\u01cb\u0001\u0000\u0000"+ - "\u0000\u06b4\u06b5\u0003\u00d8d\u0000\u06b5\u06b6\u0001\u0000\u0000\u0000"+ - "\u06b6\u06b7\u0006\u00de\u0012\u0000\u06b7\u01cd\u0001\u0000\u0000\u0000"+ - "\u06b8\u06b9\u0003\u00dcf\u0000\u06b9\u06ba\u0001\u0000\u0000\u0000\u06ba"+ - "\u06bb\u0006\u00df\u0011\u0000\u06bb\u01cf\u0001\u0000\u0000\u0000\u06bc"+ - "\u06bd\u0003\u00f4r\u0000\u06bd\u06be\u0001\u0000\u0000\u0000\u06be\u06bf"+ - "\u0006\u00e0\u001c\u0000\u06bf\u01d1\u0001\u0000\u0000\u0000\u06c0\u06c1"+ - "\u0003\u011c\u0086\u0000\u06c1\u06c2\u0001\u0000\u0000\u0000\u06c2\u06c3"+ - "\u0006\u00e1\u001d\u0000\u06c3\u01d3\u0001\u0000\u0000\u0000\u06c4\u06c5"+ - "\u0003\u0118\u0084\u0000\u06c5\u06c6\u0001\u0000\u0000\u0000\u06c6\u06c7"+ - "\u0006\u00e2\u001e\u0000\u06c7\u01d5\u0001\u0000\u0000\u0000\u06c8\u06c9"+ - "\u0003\u011e\u0087\u0000\u06c9\u06ca\u0001\u0000\u0000\u0000\u06ca\u06cb"+ - "\u0006\u00e3\u001f\u0000\u06cb\u01d7\u0001\u0000\u0000\u0000\u06cc\u06cd"+ - "\u0003\u00cc^\u0000\u06cd\u06ce\u0001\u0000\u0000\u0000\u06ce\u06cf\u0006"+ - "\u00e4\u0010\u0000\u06cf\u01d9\u0001\u0000\u0000\u0000\u06d0\u06d1\u0003"+ - "\u01c0\u00d8\u0000\u06d1\u06d2\u0001\u0000\u0000\u0000\u06d2\u06d3\u0006"+ - "\u00e5\u001b\u0000\u06d3\u01db\u0001\u0000\u0000\u0000\u06d4\u06d5\u0003"+ - "\u0010\u0000\u0000\u06d5\u06d6\u0001\u0000\u0000\u0000\u06d6\u06d7\u0006"+ - "\u00e6\u0000\u0000\u06d7\u01dd\u0001\u0000\u0000\u0000\u06d8\u06d9\u0003"+ - "\u0012\u0001\u0000\u06d9\u06da\u0001\u0000\u0000\u0000\u06da\u06db\u0006"+ - "\u00e7\u0000\u0000\u06db\u01df\u0001\u0000\u0000\u0000\u06dc\u06dd\u0003"+ - "\u0014\u0002\u0000\u06dd\u06de\u0001\u0000\u0000\u0000\u06de\u06df\u0006"+ - "\u00e8\u0000\u0000\u06df\u01e1\u0001\u0000\u0000\u0000\u06e0\u06e1\u0003"+ - "\u00aeO\u0000\u06e1\u06e2\u0001\u0000\u0000\u0000\u06e2\u06e3\u0006\u00e9"+ - "\r\u0000\u06e3\u06e4\u0006\u00e9\u000e\u0000\u06e4\u01e3\u0001\u0000\u0000"+ - "\u0000\u06e5\u06e6\u0007\n\u0000\u0000\u06e6\u06e7\u0007\u0005\u0000\u0000"+ - "\u06e7\u06e8\u0007\u0015\u0000\u0000\u06e8\u06e9\u0007\t\u0000\u0000\u06e9"+ - "\u01e5\u0001\u0000\u0000\u0000\u06ea\u06eb\u0003\u0010\u0000\u0000\u06eb"+ - "\u06ec\u0001\u0000\u0000\u0000\u06ec\u06ed\u0006\u00eb\u0000\u0000\u06ed"+ - "\u01e7\u0001\u0000\u0000\u0000\u06ee\u06ef\u0003\u0012\u0001\u0000\u06ef"+ - "\u06f0\u0001\u0000\u0000\u0000\u06f0\u06f1\u0006\u00ec\u0000\u0000\u06f1"+ - "\u01e9\u0001\u0000\u0000\u0000\u06f2\u06f3\u0003\u0014\u0002\u0000\u06f3"+ - "\u06f4\u0001\u0000\u0000\u0000\u06f4\u06f5\u0006\u00ed\u0000\u0000\u06f5"+ - "\u01eb\u0001\u0000\u0000\u0000F\u0000\u0001\u0002\u0003\u0004\u0005\u0006"+ - "\u0007\b\t\n\u000b\f\r\u000e\u000f\u01f2\u01f6\u01f9\u0202\u0204\u020f"+ - "\u0325\u036b\u036f\u0374\u03ce\u03d0\u0403\u0408\u0411\u0418\u041d\u041f"+ - "\u042a\u0432\u0435\u0437\u043c\u0441\u0447\u044e\u0453\u0459\u045c\u0464"+ - "\u0468\u04f6\u04fb\u0502\u0504\u0509\u050e\u0515\u0517\u0531\u0536\u053b"+ - "\u053d\u0543\u057e\u0583\u0682\u0686\u068b\u0690\u0695\u0697\u069b\u069d"+ - ")\u0000\u0001\u0000\u0005\u0001\u0000\u0005\u0002\u0000\u0005\u0005\u0000"+ - "\u0005\u0006\u0000\u0005\u0007\u0000\u0005\b\u0000\u0005\t\u0000\u0005"+ - "\n\u0000\u0005\f\u0000\u0005\r\u0000\u0005\u000e\u0000\u0005\u000f\u0000"+ - "\u00074\u0000\u0004\u0000\u0000\u0007K\u0000\u00079\u0000\u0007A\u0000"+ - "\u0007?\u0000\u0007g\u0000\u0007f\u0000\u0007b\u0000\u0005\u0004\u0000"+ - "\u0005\u0003\u0000\u0007P\u0000\u0007&\u0000\u0007;\u0000\u0007\u0081"+ - "\u0000\u0007M\u0000\u0007`\u0000\u0007_\u0000\u0007a\u0000\u0007c\u0000"+ - "\u0007>\u0000\u0005\u0000\u0000\u0007\u000f\u0000\u0007=\u0000\u0007l"+ - "\u0000\u00075\u0000\u0007d\u0000\u0005\u000b\u0000"; + "\u0000\u01d6\u0084\u01d8\u0000\u01da\u0085\u01dc\u0086\u01de\u0087\u01e0"+ + "\u0000\u01e2\u0088\u01e4\u0089\u01e6\u008a\u01e8\u008b\u0010\u0000\u0001"+ + "\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f$\u0002"+ + "\u0000\n\n\r\r\u0003\u0000\t\n\r\r \u0002\u0000CCcc\u0002\u0000HHhh\u0002"+ + "\u0000AAaa\u0002\u0000NNnn\u0002\u0000GGgg\u0002\u0000EEee\u0002\u0000"+ + "PPpp\u0002\u0000OOoo\u0002\u0000IIii\u0002\u0000TTtt\u0002\u0000RRrr\u0002"+ + "\u0000XXxx\u0002\u0000LLll\u0002\u0000MMmm\u0002\u0000DDdd\u0002\u0000"+ + "SSss\u0002\u0000VVvv\u0002\u0000KKkk\u0002\u0000WWww\u0002\u0000FFff\u0002"+ + "\u0000UUuu\u0006\u0000\t\n\r\r //[[]]\u000b\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**//\u0002"+ + "\u0000JJjj\u070f\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\u00002\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\u0000B\u0001\u0000\u0000\u0000"+ + "\u0000D\u0001\u0000\u0000\u0000\u0000F\u0001\u0000\u0000\u0000\u0000H"+ + "\u0001\u0000\u0000\u0000\u0000J\u0001\u0000\u0000\u0000\u0000L\u0001\u0000"+ + "\u0000\u0000\u0000N\u0001\u0000\u0000\u0000\u0000P\u0001\u0000\u0000\u0000"+ + "\u0000R\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`\u0001\u0000\u0000\u0000\u0001b\u0001\u0000\u0000\u0000\u0001"+ + "d\u0001\u0000\u0000\u0000\u0001f\u0001\u0000\u0000\u0000\u0002h\u0001"+ + "\u0000\u0000\u0000\u0002j\u0001\u0000\u0000\u0000\u0002l\u0001\u0000\u0000"+ + "\u0000\u0002n\u0001\u0000\u0000\u0000\u0002r\u0001\u0000\u0000\u0000\u0002"+ + "t\u0001\u0000\u0000\u0000\u0002v\u0001\u0000\u0000\u0000\u0002x\u0001"+ + "\u0000\u0000\u0000\u0002z\u0001\u0000\u0000\u0000\u0003|\u0001\u0000\u0000"+ + "\u0000\u0003~\u0001\u0000\u0000\u0000\u0003\u0080\u0001\u0000\u0000\u0000"+ + "\u0003\u0082\u0001\u0000\u0000\u0000\u0003\u0084\u0001\u0000\u0000\u0000"+ + "\u0003\u0086\u0001\u0000\u0000\u0000\u0003\u0088\u0001\u0000\u0000\u0000"+ + "\u0003\u008a\u0001\u0000\u0000\u0000\u0003\u008c\u0001\u0000\u0000\u0000"+ + "\u0003\u008e\u0001\u0000\u0000\u0000\u0003\u0090\u0001\u0000\u0000\u0000"+ + "\u0003\u0092\u0001\u0000\u0000\u0000\u0003\u0094\u0001\u0000\u0000\u0000"+ + "\u0003\u0096\u0001\u0000\u0000\u0000\u0004\u0098\u0001\u0000\u0000\u0000"+ + "\u0004\u009a\u0001\u0000\u0000\u0000\u0004\u009c\u0001\u0000\u0000\u0000"+ + "\u0004\u009e\u0001\u0000\u0000\u0000\u0004\u00a0\u0001\u0000\u0000\u0000"+ + "\u0004\u00a2\u0001\u0000\u0000\u0000\u0005\u00a4\u0001\u0000\u0000\u0000"+ + "\u0005\u00a6\u0001\u0000\u0000\u0000\u0005\u00a8\u0001\u0000\u0000\u0000"+ + "\u0005\u00aa\u0001\u0000\u0000\u0000\u0005\u00ac\u0001\u0000\u0000\u0000"+ + "\u0006\u00ae\u0001\u0000\u0000\u0000\u0006\u00c4\u0001\u0000\u0000\u0000"+ + "\u0006\u00c6\u0001\u0000\u0000\u0000\u0006\u00c8\u0001\u0000\u0000\u0000"+ + "\u0006\u00ca\u0001\u0000\u0000\u0000\u0006\u00cc\u0001\u0000\u0000\u0000"+ + "\u0006\u00ce\u0001\u0000\u0000\u0000\u0006\u00d0\u0001\u0000\u0000\u0000"+ + "\u0006\u00d2\u0001\u0000\u0000\u0000\u0006\u00d4\u0001\u0000\u0000\u0000"+ + "\u0006\u00d6\u0001\u0000\u0000\u0000\u0006\u00d8\u0001\u0000\u0000\u0000"+ + "\u0006\u00da\u0001\u0000\u0000\u0000\u0006\u00dc\u0001\u0000\u0000\u0000"+ + "\u0006\u00de\u0001\u0000\u0000\u0000\u0006\u00e0\u0001\u0000\u0000\u0000"+ + "\u0006\u00e2\u0001\u0000\u0000\u0000\u0006\u00e4\u0001\u0000\u0000\u0000"+ + "\u0006\u00e6\u0001\u0000\u0000\u0000\u0006\u00e8\u0001\u0000\u0000\u0000"+ + "\u0006\u00ea\u0001\u0000\u0000\u0000\u0006\u00ec\u0001\u0000\u0000\u0000"+ + "\u0006\u00ee\u0001\u0000\u0000\u0000\u0006\u00f0\u0001\u0000\u0000\u0000"+ + "\u0006\u00f2\u0001\u0000\u0000\u0000\u0006\u00f4\u0001\u0000\u0000\u0000"+ + "\u0006\u00f6\u0001\u0000\u0000\u0000\u0006\u00f8\u0001\u0000\u0000\u0000"+ + "\u0006\u00fa\u0001\u0000\u0000\u0000\u0006\u00fc\u0001\u0000\u0000\u0000"+ + "\u0006\u00fe\u0001\u0000\u0000\u0000\u0006\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\u010a\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"+ + "\u0006\u0116\u0001\u0000\u0000\u0000\u0006\u0118\u0001\u0000\u0000\u0000"+ + "\u0006\u011a\u0001\u0000\u0000\u0000\u0006\u011c\u0001\u0000\u0000\u0000"+ + "\u0006\u011e\u0001\u0000\u0000\u0000\u0006\u0120\u0001\u0000\u0000\u0000"+ + "\u0006\u0122\u0001\u0000\u0000\u0000\u0006\u0124\u0001\u0000\u0000\u0000"+ + "\u0006\u0126\u0001\u0000\u0000\u0000\u0006\u012a\u0001\u0000\u0000\u0000"+ + "\u0006\u012c\u0001\u0000\u0000\u0000\u0006\u012e\u0001\u0000\u0000\u0000"+ + "\u0006\u0130\u0001\u0000\u0000\u0000\u0007\u0132\u0001\u0000\u0000\u0000"+ + "\u0007\u0134\u0001\u0000\u0000\u0000\u0007\u0136\u0001\u0000\u0000\u0000"+ + "\u0007\u0138\u0001\u0000\u0000\u0000\u0007\u013a\u0001\u0000\u0000\u0000"+ + "\u0007\u013c\u0001\u0000\u0000\u0000\u0007\u013e\u0001\u0000\u0000\u0000"+ + "\u0007\u0140\u0001\u0000\u0000\u0000\u0007\u0144\u0001\u0000\u0000\u0000"+ + "\u0007\u0146\u0001\u0000\u0000\u0000\u0007\u0148\u0001\u0000\u0000\u0000"+ + "\u0007\u014a\u0001\u0000\u0000\u0000\u0007\u014c\u0001\u0000\u0000\u0000"+ + "\u0007\u014e\u0001\u0000\u0000\u0000\b\u0150\u0001\u0000\u0000\u0000\b"+ + "\u0152\u0001\u0000\u0000\u0000\b\u0154\u0001\u0000\u0000\u0000\b\u0156"+ + "\u0001\u0000\u0000\u0000\b\u0158\u0001\u0000\u0000\u0000\t\u015a\u0001"+ + "\u0000\u0000\u0000\t\u015c\u0001\u0000\u0000\u0000\t\u015e\u0001\u0000"+ + "\u0000\u0000\t\u0160\u0001\u0000\u0000\u0000\t\u0162\u0001\u0000\u0000"+ + "\u0000\t\u0164\u0001\u0000\u0000\u0000\t\u0166\u0001\u0000\u0000\u0000"+ + "\t\u0168\u0001\u0000\u0000\u0000\t\u016a\u0001\u0000\u0000\u0000\t\u016c"+ + "\u0001\u0000\u0000\u0000\t\u016e\u0001\u0000\u0000\u0000\t\u0170\u0001"+ + "\u0000\u0000\u0000\t\u0172\u0001\u0000\u0000\u0000\n\u0174\u0001\u0000"+ + "\u0000\u0000\n\u0176\u0001\u0000\u0000\u0000\n\u0178\u0001\u0000\u0000"+ + "\u0000\n\u017a\u0001\u0000\u0000\u0000\n\u017c\u0001\u0000\u0000\u0000"+ + "\n\u017e\u0001\u0000\u0000\u0000\n\u0180\u0001\u0000\u0000\u0000\n\u0182"+ + "\u0001\u0000\u0000\u0000\n\u0184\u0001\u0000\u0000\u0000\n\u0186\u0001"+ + "\u0000\u0000\u0000\u000b\u0188\u0001\u0000\u0000\u0000\u000b\u018a\u0001"+ + "\u0000\u0000\u0000\u000b\u018c\u0001\u0000\u0000\u0000\u000b\u018e\u0001"+ + "\u0000\u0000\u0000\u000b\u0190\u0001\u0000\u0000\u0000\u000b\u0192\u0001"+ + "\u0000\u0000\u0000\u000b\u0194\u0001\u0000\u0000\u0000\f\u0196\u0001\u0000"+ + "\u0000\u0000\f\u0198\u0001\u0000\u0000\u0000\f\u019a\u0001\u0000\u0000"+ + "\u0000\f\u019c\u0001\u0000\u0000\u0000\f\u019e\u0001\u0000\u0000\u0000"+ + "\f\u01a0\u0001\u0000\u0000\u0000\f\u01a2\u0001\u0000\u0000\u0000\f\u01a4"+ + "\u0001\u0000\u0000\u0000\f\u01a6\u0001\u0000\u0000\u0000\f\u01a8\u0001"+ + "\u0000\u0000\u0000\f\u01aa\u0001\u0000\u0000\u0000\r\u01ac\u0001\u0000"+ + "\u0000\u0000\r\u01ae\u0001\u0000\u0000\u0000\r\u01b0\u0001\u0000\u0000"+ + "\u0000\r\u01b2\u0001\u0000\u0000\u0000\r\u01b4\u0001\u0000\u0000\u0000"+ + "\r\u01b6\u0001\u0000\u0000\u0000\r\u01b8\u0001\u0000\u0000\u0000\r\u01be"+ + "\u0001\u0000\u0000\u0000\r\u01c0\u0001\u0000\u0000\u0000\r\u01c2\u0001"+ + "\u0000\u0000\u0000\r\u01c4\u0001\u0000\u0000\u0000\u000e\u01c6\u0001\u0000"+ + "\u0000\u0000\u000e\u01c8\u0001\u0000\u0000\u0000\u000e\u01ca\u0001\u0000"+ + "\u0000\u0000\u000e\u01cc\u0001\u0000\u0000\u0000\u000e\u01ce\u0001\u0000"+ + "\u0000\u0000\u000e\u01d0\u0001\u0000\u0000\u0000\u000e\u01d2\u0001\u0000"+ + "\u0000\u0000\u000e\u01d4\u0001\u0000\u0000\u0000\u000e\u01d6\u0001\u0000"+ + "\u0000\u0000\u000e\u01d8\u0001\u0000\u0000\u0000\u000e\u01da\u0001\u0000"+ + "\u0000\u0000\u000e\u01dc\u0001\u0000\u0000\u0000\u000e\u01de\u0001\u0000"+ + "\u0000\u0000\u000f\u01e0\u0001\u0000\u0000\u0000\u000f\u01e2\u0001\u0000"+ + "\u0000\u0000\u000f\u01e4\u0001\u0000\u0000\u0000\u000f\u01e6\u0001\u0000"+ + "\u0000\u0000\u000f\u01e8\u0001\u0000\u0000\u0000\u0010\u01ea\u0001\u0000"+ + "\u0000\u0000\u0012\u01fb\u0001\u0000\u0000\u0000\u0014\u020b\u0001\u0000"+ + "\u0000\u0000\u0016\u0211\u0001\u0000\u0000\u0000\u0018\u0220\u0001\u0000"+ + "\u0000\u0000\u001a\u0229\u0001\u0000\u0000\u0000\u001c\u0233\u0001\u0000"+ + "\u0000\u0000\u001e\u0240\u0001\u0000\u0000\u0000 \u024a\u0001\u0000\u0000"+ + "\u0000\"\u0251\u0001\u0000\u0000\u0000$\u0258\u0001\u0000\u0000\u0000"+ + "&\u0260\u0001\u0000\u0000\u0000(\u0266\u0001\u0000\u0000\u0000*\u026d"+ + "\u0001\u0000\u0000\u0000,\u0275\u0001\u0000\u0000\u0000.\u027d\u0001\u0000"+ + "\u0000\u00000\u028c\u0001\u0000\u0000\u00002\u0296\u0001\u0000\u0000\u0000"+ + "4\u02a0\u0001\u0000\u0000\u00006\u02a7\u0001\u0000\u0000\u00008\u02ad"+ + "\u0001\u0000\u0000\u0000:\u02b5\u0001\u0000\u0000\u0000<\u02be\u0001\u0000"+ + "\u0000\u0000>\u02c6\u0001\u0000\u0000\u0000@\u02ce\u0001\u0000\u0000\u0000"+ + "B\u02d7\u0001\u0000\u0000\u0000D\u02e3\u0001\u0000\u0000\u0000F\u02ef"+ + "\u0001\u0000\u0000\u0000H\u02f6\u0001\u0000\u0000\u0000J\u02fd\u0001\u0000"+ + "\u0000\u0000L\u0309\u0001\u0000\u0000\u0000N\u0310\u0001\u0000\u0000\u0000"+ + "P\u0319\u0001\u0000\u0000\u0000R\u0321\u0001\u0000\u0000\u0000T\u0327"+ + "\u0001\u0000\u0000\u0000V\u032c\u0001\u0000\u0000\u0000X\u0330\u0001\u0000"+ + "\u0000\u0000Z\u0334\u0001\u0000\u0000\u0000\\\u0338\u0001\u0000\u0000"+ + "\u0000^\u033c\u0001\u0000\u0000\u0000`\u0340\u0001\u0000\u0000\u0000b"+ + "\u0344\u0001\u0000\u0000\u0000d\u0348\u0001\u0000\u0000\u0000f\u034c\u0001"+ + "\u0000\u0000\u0000h\u0350\u0001\u0000\u0000\u0000j\u0355\u0001\u0000\u0000"+ + "\u0000l\u035a\u0001\u0000\u0000\u0000n\u035f\u0001\u0000\u0000\u0000p"+ + "\u0364\u0001\u0000\u0000\u0000r\u036d\u0001\u0000\u0000\u0000t\u0374\u0001"+ + "\u0000\u0000\u0000v\u0378\u0001\u0000\u0000\u0000x\u037c\u0001\u0000\u0000"+ + "\u0000z\u0380\u0001\u0000\u0000\u0000|\u0384\u0001\u0000\u0000\u0000~"+ + "\u038a\u0001\u0000\u0000\u0000\u0080\u038e\u0001\u0000\u0000\u0000\u0082"+ + "\u0392\u0001\u0000\u0000\u0000\u0084\u0396\u0001\u0000\u0000\u0000\u0086"+ + "\u039a\u0001\u0000\u0000\u0000\u0088\u039e\u0001\u0000\u0000\u0000\u008a"+ + "\u03a2\u0001\u0000\u0000\u0000\u008c\u03a6\u0001\u0000\u0000\u0000\u008e"+ + "\u03aa\u0001\u0000\u0000\u0000\u0090\u03ae\u0001\u0000\u0000\u0000\u0092"+ + "\u03b2\u0001\u0000\u0000\u0000\u0094\u03b6\u0001\u0000\u0000\u0000\u0096"+ + "\u03ba\u0001\u0000\u0000\u0000\u0098\u03be\u0001\u0000\u0000\u0000\u009a"+ + "\u03c3\u0001\u0000\u0000\u0000\u009c\u03cc\u0001\u0000\u0000\u0000\u009e"+ + "\u03d0\u0001\u0000\u0000\u0000\u00a0\u03d4\u0001\u0000\u0000\u0000\u00a2"+ + "\u03d8\u0001\u0000\u0000\u0000\u00a4\u03dc\u0001\u0000\u0000\u0000\u00a6"+ + "\u03e1\u0001\u0000\u0000\u0000\u00a8\u03e6\u0001\u0000\u0000\u0000\u00aa"+ + "\u03ea\u0001\u0000\u0000\u0000\u00ac\u03ee\u0001\u0000\u0000\u0000\u00ae"+ + "\u03f2\u0001\u0000\u0000\u0000\u00b0\u03f6\u0001\u0000\u0000\u0000\u00b2"+ + "\u03f8\u0001\u0000\u0000\u0000\u00b4\u03fa\u0001\u0000\u0000\u0000\u00b6"+ + "\u03fd\u0001\u0000\u0000\u0000\u00b8\u03ff\u0001\u0000\u0000\u0000\u00ba"+ + "\u0408\u0001\u0000\u0000\u0000\u00bc\u040a\u0001\u0000\u0000\u0000\u00be"+ + "\u040f\u0001\u0000\u0000\u0000\u00c0\u0411\u0001\u0000\u0000\u0000\u00c2"+ + "\u0416\u0001\u0000\u0000\u0000\u00c4\u0435\u0001\u0000\u0000\u0000\u00c6"+ + "\u0438\u0001\u0000\u0000\u0000\u00c8\u0466\u0001\u0000\u0000\u0000\u00ca"+ + "\u0468\u0001\u0000\u0000\u0000\u00cc\u046c\u0001\u0000\u0000\u0000\u00ce"+ + "\u0470\u0001\u0000\u0000\u0000\u00d0\u0472\u0001\u0000\u0000\u0000\u00d2"+ + "\u0475\u0001\u0000\u0000\u0000\u00d4\u0478\u0001\u0000\u0000\u0000\u00d6"+ + "\u047a\u0001\u0000\u0000\u0000\u00d8\u047c\u0001\u0000\u0000\u0000\u00da"+ + "\u0481\u0001\u0000\u0000\u0000\u00dc\u0483\u0001\u0000\u0000\u0000\u00de"+ + "\u0489\u0001\u0000\u0000\u0000\u00e0\u048f\u0001\u0000\u0000\u0000\u00e2"+ + "\u0492\u0001\u0000\u0000\u0000\u00e4\u0495\u0001\u0000\u0000\u0000\u00e6"+ + "\u049a\u0001\u0000\u0000\u0000\u00e8\u049f\u0001\u0000\u0000\u0000\u00ea"+ + "\u04a3\u0001\u0000\u0000\u0000\u00ec\u04a8\u0001\u0000\u0000\u0000\u00ee"+ + "\u04ae\u0001\u0000\u0000\u0000\u00f0\u04b1\u0001\u0000\u0000\u0000\u00f2"+ + "\u04b4\u0001\u0000\u0000\u0000\u00f4\u04b6\u0001\u0000\u0000\u0000\u00f6"+ + "\u04bc\u0001\u0000\u0000\u0000\u00f8\u04c1\u0001\u0000\u0000\u0000\u00fa"+ + "\u04c6\u0001\u0000\u0000\u0000\u00fc\u04c9\u0001\u0000\u0000\u0000\u00fe"+ + "\u04cc\u0001\u0000\u0000\u0000\u0100\u04cf\u0001\u0000\u0000\u0000\u0102"+ + "\u04d1\u0001\u0000\u0000\u0000\u0104\u04d4\u0001\u0000\u0000\u0000\u0106"+ + "\u04d6\u0001\u0000\u0000\u0000\u0108\u04d9\u0001\u0000\u0000\u0000\u010a"+ + "\u04db\u0001\u0000\u0000\u0000\u010c\u04dd\u0001\u0000\u0000\u0000\u010e"+ + "\u04df\u0001\u0000\u0000\u0000\u0110\u04e1\u0001\u0000\u0000\u0000\u0112"+ + "\u04e3\u0001\u0000\u0000\u0000\u0114\u04e5\u0001\u0000\u0000\u0000\u0116"+ + "\u04e7\u0001\u0000\u0000\u0000\u0118\u04ea\u0001\u0000\u0000\u0000\u011a"+ + "\u04ff\u0001\u0000\u0000\u0000\u011c\u0512\u0001\u0000\u0000\u0000\u011e"+ + "\u0514\u0001\u0000\u0000\u0000\u0120\u0519\u0001\u0000\u0000\u0000\u0122"+ + "\u051e\u0001\u0000\u0000\u0000\u0124\u0523\u0001\u0000\u0000\u0000\u0126"+ + "\u0538\u0001\u0000\u0000\u0000\u0128\u053a\u0001\u0000\u0000\u0000\u012a"+ + "\u0542\u0001\u0000\u0000\u0000\u012c\u0544\u0001\u0000\u0000\u0000\u012e"+ + "\u0548\u0001\u0000\u0000\u0000\u0130\u054c\u0001\u0000\u0000\u0000\u0132"+ + "\u0550\u0001\u0000\u0000\u0000\u0134\u0555\u0001\u0000\u0000\u0000\u0136"+ + "\u0559\u0001\u0000\u0000\u0000\u0138\u055d\u0001\u0000\u0000\u0000\u013a"+ + "\u0561\u0001\u0000\u0000\u0000\u013c\u0565\u0001\u0000\u0000\u0000\u013e"+ + "\u0569\u0001\u0000\u0000\u0000\u0140\u056d\u0001\u0000\u0000\u0000\u0142"+ + "\u0579\u0001\u0000\u0000\u0000\u0144\u057c\u0001\u0000\u0000\u0000\u0146"+ + "\u0580\u0001\u0000\u0000\u0000\u0148\u0584\u0001\u0000\u0000\u0000\u014a"+ + "\u0588\u0001\u0000\u0000\u0000\u014c\u058c\u0001\u0000\u0000\u0000\u014e"+ + "\u0590\u0001\u0000\u0000\u0000\u0150\u0594\u0001\u0000\u0000\u0000\u0152"+ + "\u0599\u0001\u0000\u0000\u0000\u0154\u059e\u0001\u0000\u0000\u0000\u0156"+ + "\u05a2\u0001\u0000\u0000\u0000\u0158\u05a6\u0001\u0000\u0000\u0000\u015a"+ + "\u05aa\u0001\u0000\u0000\u0000\u015c\u05af\u0001\u0000\u0000\u0000\u015e"+ + "\u05b4\u0001\u0000\u0000\u0000\u0160\u05b8\u0001\u0000\u0000\u0000\u0162"+ + "\u05be\u0001\u0000\u0000\u0000\u0164\u05c7\u0001\u0000\u0000\u0000\u0166"+ + "\u05cb\u0001\u0000\u0000\u0000\u0168\u05cf\u0001\u0000\u0000\u0000\u016a"+ + "\u05d3\u0001\u0000\u0000\u0000\u016c\u05d7\u0001\u0000\u0000\u0000\u016e"+ + "\u05db\u0001\u0000\u0000\u0000\u0170\u05df\u0001\u0000\u0000\u0000\u0172"+ + "\u05e3\u0001\u0000\u0000\u0000\u0174\u05e7\u0001\u0000\u0000\u0000\u0176"+ + "\u05ec\u0001\u0000\u0000\u0000\u0178\u05f0\u0001\u0000\u0000\u0000\u017a"+ + "\u05f4\u0001\u0000\u0000\u0000\u017c\u05f8\u0001\u0000\u0000\u0000\u017e"+ + "\u05fd\u0001\u0000\u0000\u0000\u0180\u0601\u0001\u0000\u0000\u0000\u0182"+ + "\u0605\u0001\u0000\u0000\u0000\u0184\u0609\u0001\u0000\u0000\u0000\u0186"+ + "\u060d\u0001\u0000\u0000\u0000\u0188\u0611\u0001\u0000\u0000\u0000\u018a"+ + "\u0617\u0001\u0000\u0000\u0000\u018c\u061b\u0001\u0000\u0000\u0000\u018e"+ + "\u061f\u0001\u0000\u0000\u0000\u0190\u0623\u0001\u0000\u0000\u0000\u0192"+ + "\u0627\u0001\u0000\u0000\u0000\u0194\u062b\u0001\u0000\u0000\u0000\u0196"+ + "\u062f\u0001\u0000\u0000\u0000\u0198\u0634\u0001\u0000\u0000\u0000\u019a"+ + "\u0638\u0001\u0000\u0000\u0000\u019c\u063c\u0001\u0000\u0000\u0000\u019e"+ + "\u0640\u0001\u0000\u0000\u0000\u01a0\u0644\u0001\u0000\u0000\u0000\u01a2"+ + "\u0648\u0001\u0000\u0000\u0000\u01a4\u064c\u0001\u0000\u0000\u0000\u01a6"+ + "\u0650\u0001\u0000\u0000\u0000\u01a8\u0654\u0001\u0000\u0000\u0000\u01aa"+ + "\u0658\u0001\u0000\u0000\u0000\u01ac\u065c\u0001\u0000\u0000\u0000\u01ae"+ + "\u0661\u0001\u0000\u0000\u0000\u01b0\u0665\u0001\u0000\u0000\u0000\u01b2"+ + "\u0669\u0001\u0000\u0000\u0000\u01b4\u066d\u0001\u0000\u0000\u0000\u01b6"+ + "\u0671\u0001\u0000\u0000\u0000\u01b8\u0675\u0001\u0000\u0000\u0000\u01ba"+ + "\u067d\u0001\u0000\u0000\u0000\u01bc\u0692\u0001\u0000\u0000\u0000\u01be"+ + "\u0696\u0001\u0000\u0000\u0000\u01c0\u069a\u0001\u0000\u0000\u0000\u01c2"+ + "\u069e\u0001\u0000\u0000\u0000\u01c4\u06a2\u0001\u0000\u0000\u0000\u01c6"+ + "\u06a6\u0001\u0000\u0000\u0000\u01c8\u06ab\u0001\u0000\u0000\u0000\u01ca"+ + "\u06af\u0001\u0000\u0000\u0000\u01cc\u06b3\u0001\u0000\u0000\u0000\u01ce"+ + "\u06b7\u0001\u0000\u0000\u0000\u01d0\u06bb\u0001\u0000\u0000\u0000\u01d2"+ + "\u06bf\u0001\u0000\u0000\u0000\u01d4\u06c3\u0001\u0000\u0000\u0000\u01d6"+ + "\u06c7\u0001\u0000\u0000\u0000\u01d8\u06ca\u0001\u0000\u0000\u0000\u01da"+ + "\u06ce\u0001\u0000\u0000\u0000\u01dc\u06d2\u0001\u0000\u0000\u0000\u01de"+ + "\u06d6\u0001\u0000\u0000\u0000\u01e0\u06da\u0001\u0000\u0000\u0000\u01e2"+ + "\u06df\u0001\u0000\u0000\u0000\u01e4\u06e4\u0001\u0000\u0000\u0000\u01e6"+ + "\u06e8\u0001\u0000\u0000\u0000\u01e8\u06ec\u0001\u0000\u0000\u0000\u01ea"+ + "\u01eb\u0005/\u0000\u0000\u01eb\u01ec\u0005/\u0000\u0000\u01ec\u01f0\u0001"+ + "\u0000\u0000\u0000\u01ed\u01ef\b\u0000\u0000\u0000\u01ee\u01ed\u0001\u0000"+ + "\u0000\u0000\u01ef\u01f2\u0001\u0000\u0000\u0000\u01f0\u01ee\u0001\u0000"+ + "\u0000\u0000\u01f0\u01f1\u0001\u0000\u0000\u0000\u01f1\u01f4\u0001\u0000"+ + "\u0000\u0000\u01f2\u01f0\u0001\u0000\u0000\u0000\u01f3\u01f5\u0005\r\u0000"+ + "\u0000\u01f4\u01f3\u0001\u0000\u0000\u0000\u01f4\u01f5\u0001\u0000\u0000"+ + "\u0000\u01f5\u01f7\u0001\u0000\u0000\u0000\u01f6\u01f8\u0005\n\u0000\u0000"+ + "\u01f7\u01f6\u0001\u0000\u0000\u0000\u01f7\u01f8\u0001\u0000\u0000\u0000"+ + "\u01f8\u01f9\u0001\u0000\u0000\u0000\u01f9\u01fa\u0006\u0000\u0000\u0000"+ + "\u01fa\u0011\u0001\u0000\u0000\u0000\u01fb\u01fc\u0005/\u0000\u0000\u01fc"+ + "\u01fd\u0005*\u0000\u0000\u01fd\u0202\u0001\u0000\u0000\u0000\u01fe\u0201"+ + "\u0003\u0012\u0001\u0000\u01ff\u0201\t\u0000\u0000\u0000\u0200\u01fe\u0001"+ + "\u0000\u0000\u0000\u0200\u01ff\u0001\u0000\u0000\u0000\u0201\u0204\u0001"+ + "\u0000\u0000\u0000\u0202\u0203\u0001\u0000\u0000\u0000\u0202\u0200\u0001"+ + "\u0000\u0000\u0000\u0203\u0205\u0001\u0000\u0000\u0000\u0204\u0202\u0001"+ + "\u0000\u0000\u0000\u0205\u0206\u0005*\u0000\u0000\u0206\u0207\u0005/\u0000"+ + "\u0000\u0207\u0208\u0001\u0000\u0000\u0000\u0208\u0209\u0006\u0001\u0000"+ + "\u0000\u0209\u0013\u0001\u0000\u0000\u0000\u020a\u020c\u0007\u0001\u0000"+ + "\u0000\u020b\u020a\u0001\u0000\u0000\u0000\u020c\u020d\u0001\u0000\u0000"+ + "\u0000\u020d\u020b\u0001\u0000\u0000\u0000\u020d\u020e\u0001\u0000\u0000"+ + "\u0000\u020e\u020f\u0001\u0000\u0000\u0000\u020f\u0210\u0006\u0002\u0000"+ + "\u0000\u0210\u0015\u0001\u0000\u0000\u0000\u0211\u0212\u0007\u0002\u0000"+ + "\u0000\u0212\u0213\u0007\u0003\u0000\u0000\u0213\u0214\u0007\u0004\u0000"+ + "\u0000\u0214\u0215\u0007\u0005\u0000\u0000\u0215\u0216\u0007\u0006\u0000"+ + "\u0000\u0216\u0217\u0007\u0007\u0000\u0000\u0217\u0218\u0005_\u0000\u0000"+ + "\u0218\u0219\u0007\b\u0000\u0000\u0219\u021a\u0007\t\u0000\u0000\u021a"+ + "\u021b\u0007\n\u0000\u0000\u021b\u021c\u0007\u0005\u0000\u0000\u021c\u021d"+ + "\u0007\u000b\u0000\u0000\u021d\u021e\u0001\u0000\u0000\u0000\u021e\u021f"+ + "\u0006\u0003\u0001\u0000\u021f\u0017\u0001\u0000\u0000\u0000\u0220\u0221"+ + "\u0007\u0007\u0000\u0000\u0221\u0222\u0007\u0005\u0000\u0000\u0222\u0223"+ + "\u0007\f\u0000\u0000\u0223\u0224\u0007\n\u0000\u0000\u0224\u0225\u0007"+ + "\u0002\u0000\u0000\u0225\u0226\u0007\u0003\u0000\u0000\u0226\u0227\u0001"+ + "\u0000\u0000\u0000\u0227\u0228\u0006\u0004\u0002\u0000\u0228\u0019\u0001"+ + "\u0000\u0000\u0000\u0229\u022a\u0007\u0007\u0000\u0000\u022a\u022b\u0007"+ + "\r\u0000\u0000\u022b\u022c\u0007\b\u0000\u0000\u022c\u022d\u0007\u000e"+ + "\u0000\u0000\u022d\u022e\u0007\u0004\u0000\u0000\u022e\u022f\u0007\n\u0000"+ + "\u0000\u022f\u0230\u0007\u0005\u0000\u0000\u0230\u0231\u0001\u0000\u0000"+ + "\u0000\u0231\u0232\u0006\u0005\u0003\u0000\u0232\u001b\u0001\u0000\u0000"+ + "\u0000\u0233\u0234\u0007\u0002\u0000\u0000\u0234\u0235\u0007\t\u0000\u0000"+ + "\u0235\u0236\u0007\u000f\u0000\u0000\u0236\u0237\u0007\b\u0000\u0000\u0237"+ + "\u0238\u0007\u000e\u0000\u0000\u0238\u0239\u0007\u0007\u0000\u0000\u0239"+ + "\u023a\u0007\u000b\u0000\u0000\u023a\u023b\u0007\n\u0000\u0000\u023b\u023c"+ + "\u0007\t\u0000\u0000\u023c\u023d\u0007\u0005\u0000\u0000\u023d\u023e\u0001"+ + "\u0000\u0000\u0000\u023e\u023f\u0006\u0006\u0004\u0000\u023f\u001d\u0001"+ + "\u0000\u0000\u0000\u0240\u0241\u0007\u0010\u0000\u0000\u0241\u0242\u0007"+ + "\n\u0000\u0000\u0242\u0243\u0007\u0011\u0000\u0000\u0243\u0244\u0007\u0011"+ + "\u0000\u0000\u0244\u0245\u0007\u0007\u0000\u0000\u0245\u0246\u0007\u0002"+ + "\u0000\u0000\u0246\u0247\u0007\u000b\u0000\u0000\u0247\u0248\u0001\u0000"+ + "\u0000\u0000\u0248\u0249\u0006\u0007\u0004\u0000\u0249\u001f\u0001\u0000"+ + "\u0000\u0000\u024a\u024b\u0007\u0007\u0000\u0000\u024b\u024c\u0007\u0012"+ + "\u0000\u0000\u024c\u024d\u0007\u0004\u0000\u0000\u024d\u024e\u0007\u000e"+ + "\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250\u0006\b\u0004"+ + "\u0000\u0250!\u0001\u0000\u0000\u0000\u0251\u0252\u0007\u0006\u0000\u0000"+ + "\u0252\u0253\u0007\f\u0000\u0000\u0253\u0254\u0007\t\u0000\u0000\u0254"+ + "\u0255\u0007\u0013\u0000\u0000\u0255\u0256\u0001\u0000\u0000\u0000\u0256"+ + "\u0257\u0006\t\u0004\u0000\u0257#\u0001\u0000\u0000\u0000\u0258\u0259"+ + "\u0007\u000e\u0000\u0000\u0259\u025a\u0007\n\u0000\u0000\u025a\u025b\u0007"+ + "\u000f\u0000\u0000\u025b\u025c\u0007\n\u0000\u0000\u025c\u025d\u0007\u000b"+ + "\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f\u0006\n\u0004"+ + "\u0000\u025f%\u0001\u0000\u0000\u0000\u0260\u0261\u0007\f\u0000\u0000"+ + "\u0261\u0262\u0007\t\u0000\u0000\u0262\u0263\u0007\u0014\u0000\u0000\u0263"+ + "\u0264\u0001\u0000\u0000\u0000\u0264\u0265\u0006\u000b\u0004\u0000\u0265"+ + "\'\u0001\u0000\u0000\u0000\u0266\u0267\u0007\u0011\u0000\u0000\u0267\u0268"+ + "\u0007\t\u0000\u0000\u0268\u0269\u0007\f\u0000\u0000\u0269\u026a\u0007"+ + "\u000b\u0000\u0000\u026a\u026b\u0001\u0000\u0000\u0000\u026b\u026c\u0006"+ + "\f\u0004\u0000\u026c)\u0001\u0000\u0000\u0000\u026d\u026e\u0007\u0011"+ + "\u0000\u0000\u026e\u026f\u0007\u000b\u0000\u0000\u026f\u0270\u0007\u0004"+ + "\u0000\u0000\u0270\u0271\u0007\u000b\u0000\u0000\u0271\u0272\u0007\u0011"+ + "\u0000\u0000\u0272\u0273\u0001\u0000\u0000\u0000\u0273\u0274\u0006\r\u0004"+ + "\u0000\u0274+\u0001\u0000\u0000\u0000\u0275\u0276\u0007\u0014\u0000\u0000"+ + "\u0276\u0277\u0007\u0003\u0000\u0000\u0277\u0278\u0007\u0007\u0000\u0000"+ + "\u0278\u0279\u0007\f\u0000\u0000\u0279\u027a\u0007\u0007\u0000\u0000\u027a"+ + "\u027b\u0001\u0000\u0000\u0000\u027b\u027c\u0006\u000e\u0004\u0000\u027c"+ + "-\u0001\u0000\u0000\u0000\u027d\u027e\u0004\u000f\u0000\u0000\u027e\u027f"+ + "\u0007\n\u0000\u0000\u027f\u0280\u0007\u0005\u0000\u0000\u0280\u0281\u0007"+ + "\u000e\u0000\u0000\u0281\u0282\u0007\n\u0000\u0000\u0282\u0283\u0007\u0005"+ + "\u0000\u0000\u0283\u0284\u0007\u0007\u0000\u0000\u0284\u0285\u0007\u0011"+ + "\u0000\u0000\u0285\u0286\u0007\u000b\u0000\u0000\u0286\u0287\u0007\u0004"+ + "\u0000\u0000\u0287\u0288\u0007\u000b\u0000\u0000\u0288\u0289\u0007\u0011"+ + "\u0000\u0000\u0289\u028a\u0001\u0000\u0000\u0000\u028a\u028b\u0006\u000f"+ + "\u0004\u0000\u028b/\u0001\u0000\u0000\u0000\u028c\u028d\u0004\u0010\u0001"+ + "\u0000\u028d\u028e\u0007\f\u0000\u0000\u028e\u028f\u0007\u0007\u0000\u0000"+ + "\u028f\u0290\u0007\f\u0000\u0000\u0290\u0291\u0007\u0004\u0000\u0000\u0291"+ + "\u0292\u0007\u0005\u0000\u0000\u0292\u0293\u0007\u0013\u0000\u0000\u0293"+ + "\u0294\u0001\u0000\u0000\u0000\u0294\u0295\u0006\u0010\u0004\u0000\u0295"+ + "1\u0001\u0000\u0000\u0000\u0296\u0297\u0004\u0011\u0002\u0000\u0297\u0298"+ + "\u0007\u0011\u0000\u0000\u0298\u0299\u0007\u0004\u0000\u0000\u0299\u029a"+ + "\u0007\u000f\u0000\u0000\u029a\u029b\u0007\b\u0000\u0000\u029b\u029c\u0007"+ + "\u000e\u0000\u0000\u029c\u029d\u0007\u0007\u0000\u0000\u029d\u029e\u0001"+ + "\u0000\u0000\u0000\u029e\u029f\u0006\u0011\u0004\u0000\u029f3\u0001\u0000"+ + "\u0000\u0000\u02a0\u02a1\u0007\u0015\u0000\u0000\u02a1\u02a2\u0007\f\u0000"+ + "\u0000\u02a2\u02a3\u0007\t\u0000\u0000\u02a3\u02a4\u0007\u000f\u0000\u0000"+ + "\u02a4\u02a5\u0001\u0000\u0000\u0000\u02a5\u02a6\u0006\u0012\u0005\u0000"+ + "\u02a65\u0001\u0000\u0000\u0000\u02a7\u02a8\u0004\u0013\u0003\u0000\u02a8"+ + "\u02a9\u0007\u000b\u0000\u0000\u02a9\u02aa\u0007\u0011\u0000\u0000\u02aa"+ + "\u02ab\u0001\u0000\u0000\u0000\u02ab\u02ac\u0006\u0013\u0005\u0000\u02ac"+ + "7\u0001\u0000\u0000\u0000\u02ad\u02ae\u0004\u0014\u0004\u0000\u02ae\u02af"+ + "\u0007\u0015\u0000\u0000\u02af\u02b0\u0007\t\u0000\u0000\u02b0\u02b1\u0007"+ + "\f\u0000\u0000\u02b1\u02b2\u0007\u0013\u0000\u0000\u02b2\u02b3\u0001\u0000"+ + "\u0000\u0000\u02b3\u02b4\u0006\u0014\u0006\u0000\u02b49\u0001\u0000\u0000"+ + "\u0000\u02b5\u02b6\u0007\u000e\u0000\u0000\u02b6\u02b7\u0007\t\u0000\u0000"+ + "\u02b7\u02b8\u0007\t\u0000\u0000\u02b8\u02b9\u0007\u0013\u0000\u0000\u02b9"+ + "\u02ba\u0007\u0016\u0000\u0000\u02ba\u02bb\u0007\b\u0000\u0000\u02bb\u02bc"+ + "\u0001\u0000\u0000\u0000\u02bc\u02bd\u0006\u0015\u0007\u0000\u02bd;\u0001"+ + "\u0000\u0000\u0000\u02be\u02bf\u0004\u0016\u0005\u0000\u02bf\u02c0\u0007"+ + "\u0015\u0000\u0000\u02c0\u02c1\u0007\u0016\u0000\u0000\u02c1\u02c2\u0007"+ + "\u000e\u0000\u0000\u02c2\u02c3\u0007\u000e\u0000\u0000\u02c3\u02c4\u0001"+ + "\u0000\u0000\u0000\u02c4\u02c5\u0006\u0016\u0007\u0000\u02c5=\u0001\u0000"+ + "\u0000\u0000\u02c6\u02c7\u0004\u0017\u0006\u0000\u02c7\u02c8\u0007\u000e"+ + "\u0000\u0000\u02c8\u02c9\u0007\u0007\u0000\u0000\u02c9\u02ca\u0007\u0015"+ + "\u0000\u0000\u02ca\u02cb\u0007\u000b\u0000\u0000\u02cb\u02cc\u0001\u0000"+ + "\u0000\u0000\u02cc\u02cd\u0006\u0017\u0007\u0000\u02cd?\u0001\u0000\u0000"+ + "\u0000\u02ce\u02cf\u0004\u0018\u0007\u0000\u02cf\u02d0\u0007\f\u0000\u0000"+ + "\u02d0\u02d1\u0007\n\u0000\u0000\u02d1\u02d2\u0007\u0006\u0000\u0000\u02d2"+ + "\u02d3\u0007\u0003\u0000\u0000\u02d3\u02d4\u0007\u000b\u0000\u0000\u02d4"+ + "\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d6\u0006\u0018\u0007\u0000\u02d6"+ + "A\u0001\u0000\u0000\u0000\u02d7\u02d8\u0004\u0019\b\u0000\u02d8\u02d9"+ + "\u0007\u000e\u0000\u0000\u02d9\u02da\u0007\t\u0000\u0000\u02da\u02db\u0007"+ + "\t\u0000\u0000\u02db\u02dc\u0007\u0013\u0000\u0000\u02dc\u02dd\u0007\u0016"+ + "\u0000\u0000\u02dd\u02de\u0007\b\u0000\u0000\u02de\u02df\u0005_\u0000"+ + "\u0000\u02df\u02e0\u0005\u8001\uf414\u0000\u0000\u02e0\u02e1\u0001\u0000"+ + "\u0000\u0000\u02e1\u02e2\u0006\u0019\b\u0000\u02e2C\u0001\u0000\u0000"+ + "\u0000\u02e3\u02e4\u0007\u000f\u0000\u0000\u02e4\u02e5\u0007\u0012\u0000"+ + "\u0000\u02e5\u02e6\u0005_\u0000\u0000\u02e6\u02e7\u0007\u0007\u0000\u0000"+ + "\u02e7\u02e8\u0007\r\u0000\u0000\u02e8\u02e9\u0007\b\u0000\u0000\u02e9"+ + "\u02ea\u0007\u0004\u0000\u0000\u02ea\u02eb\u0007\u0005\u0000\u0000\u02eb"+ + "\u02ec\u0007\u0010\u0000\u0000\u02ec\u02ed\u0001\u0000\u0000\u0000\u02ed"+ + "\u02ee\u0006\u001a\t\u0000\u02eeE\u0001\u0000\u0000\u0000\u02ef\u02f0"+ + "\u0007\u0010\u0000\u0000\u02f0\u02f1\u0007\f\u0000\u0000\u02f1\u02f2\u0007"+ + "\t\u0000\u0000\u02f2\u02f3\u0007\b\u0000\u0000\u02f3\u02f4\u0001\u0000"+ + "\u0000\u0000\u02f4\u02f5\u0006\u001b\n\u0000\u02f5G\u0001\u0000\u0000"+ + "\u0000\u02f6\u02f7\u0007\u0013\u0000\u0000\u02f7\u02f8\u0007\u0007\u0000"+ + "\u0000\u02f8\u02f9\u0007\u0007\u0000\u0000\u02f9\u02fa\u0007\b\u0000\u0000"+ + "\u02fa\u02fb\u0001\u0000\u0000\u0000\u02fb\u02fc\u0006\u001c\n\u0000\u02fc"+ + "I\u0001\u0000\u0000\u0000\u02fd\u02fe\u0004\u001d\t\u0000\u02fe\u02ff"+ + "\u0007\n\u0000\u0000\u02ff\u0300\u0007\u0005\u0000\u0000\u0300\u0301\u0007"+ + "\u0011\u0000\u0000\u0301\u0302\u0007\n\u0000\u0000\u0302\u0303\u0007\u0011"+ + "\u0000\u0000\u0303\u0304\u0007\u000b\u0000\u0000\u0304\u0305\u0005_\u0000"+ + "\u0000\u0305\u0306\u0005\u8001\uf414\u0000\u0000\u0306\u0307\u0001\u0000"+ + "\u0000\u0000\u0307\u0308\u0006\u001d\n\u0000\u0308K\u0001\u0000\u0000"+ + "\u0000\u0309\u030a\u0004\u001e\n\u0000\u030a\u030b\u0007\f\u0000\u0000"+ + "\u030b\u030c\u0007\f\u0000\u0000\u030c\u030d\u0007\u0015\u0000\u0000\u030d"+ + "\u030e\u0001\u0000\u0000\u0000\u030e\u030f\u0006\u001e\u0004\u0000\u030f"+ + "M\u0001\u0000\u0000\u0000\u0310\u0311\u0007\f\u0000\u0000\u0311\u0312"+ + "\u0007\u0007\u0000\u0000\u0312\u0313\u0007\u0005\u0000\u0000\u0313\u0314"+ + "\u0007\u0004\u0000\u0000\u0314\u0315\u0007\u000f\u0000\u0000\u0315\u0316"+ + "\u0007\u0007\u0000\u0000\u0316\u0317\u0001\u0000\u0000\u0000\u0317\u0318"+ + "\u0006\u001f\u000b\u0000\u0318O\u0001\u0000\u0000\u0000\u0319\u031a\u0007"+ + "\u0011\u0000\u0000\u031a\u031b\u0007\u0003\u0000\u0000\u031b\u031c\u0007"+ + "\t\u0000\u0000\u031c\u031d\u0007\u0014\u0000\u0000\u031d\u031e\u0001\u0000"+ + "\u0000\u0000\u031e\u031f\u0006 \f\u0000\u031fQ\u0001\u0000\u0000\u0000"+ + "\u0320\u0322\b\u0017\u0000\u0000\u0321\u0320\u0001\u0000\u0000\u0000\u0322"+ + "\u0323\u0001\u0000\u0000\u0000\u0323\u0321\u0001\u0000\u0000\u0000\u0323"+ + "\u0324\u0001\u0000\u0000\u0000\u0324\u0325\u0001\u0000\u0000\u0000\u0325"+ + "\u0326\u0006!\u0004\u0000\u0326S\u0001\u0000\u0000\u0000\u0327\u0328\u0003"+ + "\u00aeO\u0000\u0328\u0329\u0001\u0000\u0000\u0000\u0329\u032a\u0006\""+ + "\r\u0000\u032a\u032b\u0006\"\u000e\u0000\u032bU\u0001\u0000\u0000\u0000"+ + "\u032c\u032d\u0003\u00eeo\u0000\u032d\u032e\u0001\u0000\u0000\u0000\u032e"+ + "\u032f\u0006#\u000f\u0000\u032fW\u0001\u0000\u0000\u0000\u0330\u0331\u0003"+ + "\u01d6\u00e3\u0000\u0331\u0332\u0001\u0000\u0000\u0000\u0332\u0333\u0006"+ + "$\u0010\u0000\u0333Y\u0001\u0000\u0000\u0000\u0334\u0335\u0003\u00dae"+ + "\u0000\u0335\u0336\u0001\u0000\u0000\u0000\u0336\u0337\u0006%\u0011\u0000"+ + "\u0337[\u0001\u0000\u0000\u0000\u0338\u0339\u0003\u00d6c\u0000\u0339\u033a"+ + "\u0001\u0000\u0000\u0000\u033a\u033b\u0006&\u0012\u0000\u033b]\u0001\u0000"+ + "\u0000\u0000\u033c\u033d\u0003\u012a\u008d\u0000\u033d\u033e\u0001\u0000"+ + "\u0000\u0000\u033e\u033f\u0006\'\u0013\u0000\u033f_\u0001\u0000\u0000"+ + "\u0000\u0340\u0341\u0003\u0126\u008b\u0000\u0341\u0342\u0001\u0000\u0000"+ + "\u0000\u0342\u0343\u0006(\u0014\u0000\u0343a\u0001\u0000\u0000\u0000\u0344"+ + "\u0345\u0003\u0010\u0000\u0000\u0345\u0346\u0001\u0000\u0000\u0000\u0346"+ + "\u0347\u0006)\u0000\u0000\u0347c\u0001\u0000\u0000\u0000\u0348\u0349\u0003"+ + "\u0012\u0001\u0000\u0349\u034a\u0001\u0000\u0000\u0000\u034a\u034b\u0006"+ + "*\u0000\u0000\u034be\u0001\u0000\u0000\u0000\u034c\u034d\u0003\u0014\u0002"+ + "\u0000\u034d\u034e\u0001\u0000\u0000\u0000\u034e\u034f\u0006+\u0000\u0000"+ + "\u034fg\u0001\u0000\u0000\u0000\u0350\u0351\u0003\u00aeO\u0000\u0351\u0352"+ + "\u0001\u0000\u0000\u0000\u0352\u0353\u0006,\r\u0000\u0353\u0354\u0006"+ + ",\u000e\u0000\u0354i\u0001\u0000\u0000\u0000\u0355\u0356\u0003\u011e\u0087"+ + "\u0000\u0356\u0357\u0001\u0000\u0000\u0000\u0357\u0358\u0006-\u0015\u0000"+ + "\u0358\u0359\u0006-\u0016\u0000\u0359k\u0001\u0000\u0000\u0000\u035a\u035b"+ + "\u0003\u00eeo\u0000\u035b\u035c\u0001\u0000\u0000\u0000\u035c\u035d\u0006"+ + ".\u000f\u0000\u035d\u035e\u0006.\u0017\u0000\u035em\u0001\u0000\u0000"+ + "\u0000\u035f\u0360\u0003\u00f8t\u0000\u0360\u0361\u0001\u0000\u0000\u0000"+ + "\u0361\u0362\u0006/\u0018\u0000\u0362\u0363\u0006/\u0017\u0000\u0363o"+ + "\u0001\u0000\u0000\u0000\u0364\u0365\b\u0018\u0000\u0000\u0365q\u0001"+ + "\u0000\u0000\u0000\u0366\u0368\u0003p0\u0000\u0367\u0366\u0001\u0000\u0000"+ + "\u0000\u0368\u0369\u0001\u0000\u0000\u0000\u0369\u0367\u0001\u0000\u0000"+ + "\u0000\u0369\u036a\u0001\u0000\u0000\u0000\u036a\u036b\u0001\u0000\u0000"+ + "\u0000\u036b\u036c\u0003\u00d4b\u0000\u036c\u036e\u0001\u0000\u0000\u0000"+ + "\u036d\u0367\u0001\u0000\u0000\u0000\u036d\u036e\u0001\u0000\u0000\u0000"+ + "\u036e\u0370\u0001\u0000\u0000\u0000\u036f\u0371\u0003p0\u0000\u0370\u036f"+ + "\u0001\u0000\u0000\u0000\u0371\u0372\u0001\u0000\u0000\u0000\u0372\u0370"+ + "\u0001\u0000\u0000\u0000\u0372\u0373\u0001\u0000\u0000\u0000\u0373s\u0001"+ + "\u0000\u0000\u0000\u0374\u0375\u0003r1\u0000\u0375\u0376\u0001\u0000\u0000"+ + "\u0000\u0376\u0377\u00062\u0019\u0000\u0377u\u0001\u0000\u0000\u0000\u0378"+ + "\u0379\u0003\u0010\u0000\u0000\u0379\u037a\u0001\u0000\u0000\u0000\u037a"+ + "\u037b\u00063\u0000\u0000\u037bw\u0001\u0000\u0000\u0000\u037c\u037d\u0003"+ + "\u0012\u0001\u0000\u037d\u037e\u0001\u0000\u0000\u0000\u037e\u037f\u0006"+ + "4\u0000\u0000\u037fy\u0001\u0000\u0000\u0000\u0380\u0381\u0003\u0014\u0002"+ + "\u0000\u0381\u0382\u0001\u0000\u0000\u0000\u0382\u0383\u00065\u0000\u0000"+ + "\u0383{\u0001\u0000\u0000\u0000\u0384\u0385\u0003\u00aeO\u0000\u0385\u0386"+ + "\u0001\u0000\u0000\u0000\u0386\u0387\u00066\r\u0000\u0387\u0388\u0006"+ + "6\u000e\u0000\u0388\u0389\u00066\u000e\u0000\u0389}\u0001\u0000\u0000"+ + "\u0000\u038a\u038b\u0003\u00ce_\u0000\u038b\u038c\u0001\u0000\u0000\u0000"+ + "\u038c\u038d\u00067\u001a\u0000\u038d\u007f\u0001\u0000\u0000\u0000\u038e"+ + "\u038f\u0003\u00d6c\u0000\u038f\u0390\u0001\u0000\u0000\u0000\u0390\u0391"+ + "\u00068\u0012\u0000\u0391\u0081\u0001\u0000\u0000\u0000\u0392\u0393\u0003"+ + "\u00dae\u0000\u0393\u0394\u0001\u0000\u0000\u0000\u0394\u0395\u00069\u0011"+ + "\u0000\u0395\u0083\u0001\u0000\u0000\u0000\u0396\u0397\u0003\u00f8t\u0000"+ + "\u0397\u0398\u0001\u0000\u0000\u0000\u0398\u0399\u0006:\u0018\u0000\u0399"+ + "\u0085\u0001\u0000\u0000\u0000\u039a\u039b\u0003\u01be\u00d7\u0000\u039b"+ + "\u039c\u0001\u0000\u0000\u0000\u039c\u039d\u0006;\u001b\u0000\u039d\u0087"+ + "\u0001\u0000\u0000\u0000\u039e\u039f\u0003\u012a\u008d\u0000\u039f\u03a0"+ + "\u0001\u0000\u0000\u0000\u03a0\u03a1\u0006<\u0013\u0000\u03a1\u0089\u0001"+ + "\u0000\u0000\u0000\u03a2\u03a3\u0003\u00f2q\u0000\u03a3\u03a4\u0001\u0000"+ + "\u0000\u0000\u03a4\u03a5\u0006=\u001c\u0000\u03a5\u008b\u0001\u0000\u0000"+ + "\u0000\u03a6\u03a7\u0003\u011a\u0085\u0000\u03a7\u03a8\u0001\u0000\u0000"+ + "\u0000\u03a8\u03a9\u0006>\u001d\u0000\u03a9\u008d\u0001\u0000\u0000\u0000"+ + "\u03aa\u03ab\u0003\u0116\u0083\u0000\u03ab\u03ac\u0001\u0000\u0000\u0000"+ + "\u03ac\u03ad\u0006?\u001e\u0000\u03ad\u008f\u0001\u0000\u0000\u0000\u03ae"+ + "\u03af\u0003\u011c\u0086\u0000\u03af\u03b0\u0001\u0000\u0000\u0000\u03b0"+ + "\u03b1\u0006@\u001f\u0000\u03b1\u0091\u0001\u0000\u0000\u0000\u03b2\u03b3"+ + "\u0003\u0010\u0000\u0000\u03b3\u03b4\u0001\u0000\u0000\u0000\u03b4\u03b5"+ + "\u0006A\u0000\u0000\u03b5\u0093\u0001\u0000\u0000\u0000\u03b6\u03b7\u0003"+ + "\u0012\u0001\u0000\u03b7\u03b8\u0001\u0000\u0000\u0000\u03b8\u03b9\u0006"+ + "B\u0000\u0000\u03b9\u0095\u0001\u0000\u0000\u0000\u03ba\u03bb\u0003\u0014"+ + "\u0002\u0000\u03bb\u03bc\u0001\u0000\u0000\u0000\u03bc\u03bd\u0006C\u0000"+ + "\u0000\u03bd\u0097\u0001\u0000\u0000\u0000\u03be\u03bf\u0003\u0120\u0088"+ + "\u0000\u03bf\u03c0\u0001\u0000\u0000\u0000\u03c0\u03c1\u0006D \u0000\u03c1"+ + "\u03c2\u0006D\u000e\u0000\u03c2\u0099\u0001\u0000\u0000\u0000\u03c3\u03c4"+ + "\u0003\u00d4b\u0000\u03c4\u03c5\u0001\u0000\u0000\u0000\u03c5\u03c6\u0006"+ + "E!\u0000\u03c6\u009b\u0001\u0000\u0000\u0000\u03c7\u03cd\u0003\u00baU"+ + "\u0000\u03c8\u03cd\u0003\u00b0P\u0000\u03c9\u03cd\u0003\u00dae\u0000\u03ca"+ + "\u03cd\u0003\u00b2Q\u0000\u03cb\u03cd\u0003\u00c0X\u0000\u03cc\u03c7\u0001"+ + "\u0000\u0000\u0000\u03cc\u03c8\u0001\u0000\u0000\u0000\u03cc\u03c9\u0001"+ + "\u0000\u0000\u0000\u03cc\u03ca\u0001\u0000\u0000\u0000\u03cc\u03cb\u0001"+ + "\u0000\u0000\u0000\u03cd\u03ce\u0001\u0000\u0000\u0000\u03ce\u03cc\u0001"+ + "\u0000\u0000\u0000\u03ce\u03cf\u0001\u0000\u0000\u0000\u03cf\u009d\u0001"+ + "\u0000\u0000\u0000\u03d0\u03d1\u0003\u0010\u0000\u0000\u03d1\u03d2\u0001"+ + "\u0000\u0000\u0000\u03d2\u03d3\u0006G\u0000\u0000\u03d3\u009f\u0001\u0000"+ + "\u0000\u0000\u03d4\u03d5\u0003\u0012\u0001\u0000\u03d5\u03d6\u0001\u0000"+ + "\u0000\u0000\u03d6\u03d7\u0006H\u0000\u0000\u03d7\u00a1\u0001\u0000\u0000"+ + "\u0000\u03d8\u03d9\u0003\u0014\u0002\u0000\u03d9\u03da\u0001\u0000\u0000"+ + "\u0000\u03da\u03db\u0006I\u0000\u0000\u03db\u00a3\u0001\u0000\u0000\u0000"+ + "\u03dc\u03dd\u0003\u011e\u0087\u0000\u03dd\u03de\u0001\u0000\u0000\u0000"+ + "\u03de\u03df\u0006J\u0015\u0000\u03df\u03e0\u0006J\"\u0000\u03e0\u00a5"+ + "\u0001\u0000\u0000\u0000\u03e1\u03e2\u0003\u00aeO\u0000\u03e2\u03e3\u0001"+ + "\u0000\u0000\u0000\u03e3\u03e4\u0006K\r\u0000\u03e4\u03e5\u0006K\u000e"+ + "\u0000\u03e5\u00a7\u0001\u0000\u0000\u0000\u03e6\u03e7\u0003\u0014\u0002"+ + "\u0000\u03e7\u03e8\u0001\u0000\u0000\u0000\u03e8\u03e9\u0006L\u0000\u0000"+ + "\u03e9\u00a9\u0001\u0000\u0000\u0000\u03ea\u03eb\u0003\u0010\u0000\u0000"+ + "\u03eb\u03ec\u0001\u0000\u0000\u0000\u03ec\u03ed\u0006M\u0000\u0000\u03ed"+ + "\u00ab\u0001\u0000\u0000\u0000\u03ee\u03ef\u0003\u0012\u0001\u0000\u03ef"+ + "\u03f0\u0001\u0000\u0000\u0000\u03f0\u03f1\u0006N\u0000\u0000\u03f1\u00ad"+ + "\u0001\u0000\u0000\u0000\u03f2\u03f3\u0005|\u0000\u0000\u03f3\u03f4\u0001"+ + "\u0000\u0000\u0000\u03f4\u03f5\u0006O\u000e\u0000\u03f5\u00af\u0001\u0000"+ + "\u0000\u0000\u03f6\u03f7\u0007\u0019\u0000\u0000\u03f7\u00b1\u0001\u0000"+ + "\u0000\u0000\u03f8\u03f9\u0007\u001a\u0000\u0000\u03f9\u00b3\u0001\u0000"+ + "\u0000\u0000\u03fa\u03fb\u0005\\\u0000\u0000\u03fb\u03fc\u0007\u001b\u0000"+ + "\u0000\u03fc\u00b5\u0001\u0000\u0000\u0000\u03fd\u03fe\b\u001c\u0000\u0000"+ + "\u03fe\u00b7\u0001\u0000\u0000\u0000\u03ff\u0401\u0007\u0007\u0000\u0000"+ + "\u0400\u0402\u0007\u001d\u0000\u0000\u0401\u0400\u0001\u0000\u0000\u0000"+ + "\u0401\u0402\u0001\u0000\u0000\u0000\u0402\u0404\u0001\u0000\u0000\u0000"+ + "\u0403\u0405\u0003\u00b0P\u0000\u0404\u0403\u0001\u0000\u0000\u0000\u0405"+ + "\u0406\u0001\u0000\u0000\u0000\u0406\u0404\u0001\u0000\u0000\u0000\u0406"+ + "\u0407\u0001\u0000\u0000\u0000\u0407\u00b9\u0001\u0000\u0000\u0000\u0408"+ + "\u0409\u0005@\u0000\u0000\u0409\u00bb\u0001\u0000\u0000\u0000\u040a\u040b"+ + "\u0005`\u0000\u0000\u040b\u00bd\u0001\u0000\u0000\u0000\u040c\u0410\b"+ + "\u001e\u0000\u0000\u040d\u040e\u0005`\u0000\u0000\u040e\u0410\u0005`\u0000"+ + "\u0000\u040f\u040c\u0001\u0000\u0000\u0000\u040f\u040d\u0001\u0000\u0000"+ + "\u0000\u0410\u00bf\u0001\u0000\u0000\u0000\u0411\u0412\u0005_\u0000\u0000"+ + "\u0412\u00c1\u0001\u0000\u0000\u0000\u0413\u0417\u0003\u00b2Q\u0000\u0414"+ + "\u0417\u0003\u00b0P\u0000\u0415\u0417\u0003\u00c0X\u0000\u0416\u0413\u0001"+ + "\u0000\u0000\u0000\u0416\u0414\u0001\u0000\u0000\u0000\u0416\u0415\u0001"+ + "\u0000\u0000\u0000\u0417\u00c3\u0001\u0000\u0000\u0000\u0418\u041d\u0005"+ + "\"\u0000\u0000\u0419\u041c\u0003\u00b4R\u0000\u041a\u041c\u0003\u00b6"+ + "S\u0000\u041b\u0419\u0001\u0000\u0000\u0000\u041b\u041a\u0001\u0000\u0000"+ + "\u0000\u041c\u041f\u0001\u0000\u0000\u0000\u041d\u041b\u0001\u0000\u0000"+ + "\u0000\u041d\u041e\u0001\u0000\u0000\u0000\u041e\u0420\u0001\u0000\u0000"+ + "\u0000\u041f\u041d\u0001\u0000\u0000\u0000\u0420\u0436\u0005\"\u0000\u0000"+ + "\u0421\u0422\u0005\"\u0000\u0000\u0422\u0423\u0005\"\u0000\u0000\u0423"+ + "\u0424\u0005\"\u0000\u0000\u0424\u0428\u0001\u0000\u0000\u0000\u0425\u0427"+ + "\b\u0000\u0000\u0000\u0426\u0425\u0001\u0000\u0000\u0000\u0427\u042a\u0001"+ + "\u0000\u0000\u0000\u0428\u0429\u0001\u0000\u0000\u0000\u0428\u0426\u0001"+ + "\u0000\u0000\u0000\u0429\u042b\u0001\u0000\u0000\u0000\u042a\u0428\u0001"+ + "\u0000\u0000\u0000\u042b\u042c\u0005\"\u0000\u0000\u042c\u042d\u0005\""+ + "\u0000\u0000\u042d\u042e\u0005\"\u0000\u0000\u042e\u0430\u0001\u0000\u0000"+ + "\u0000\u042f\u0431\u0005\"\u0000\u0000\u0430\u042f\u0001\u0000\u0000\u0000"+ + "\u0430\u0431\u0001\u0000\u0000\u0000\u0431\u0433\u0001\u0000\u0000\u0000"+ + "\u0432\u0434\u0005\"\u0000\u0000\u0433\u0432\u0001\u0000\u0000\u0000\u0433"+ + "\u0434\u0001\u0000\u0000\u0000\u0434\u0436\u0001\u0000\u0000\u0000\u0435"+ + "\u0418\u0001\u0000\u0000\u0000\u0435\u0421\u0001\u0000\u0000\u0000\u0436"+ + "\u00c5\u0001\u0000\u0000\u0000\u0437\u0439\u0003\u00b0P\u0000\u0438\u0437"+ + "\u0001\u0000\u0000\u0000\u0439\u043a\u0001\u0000\u0000\u0000\u043a\u0438"+ + "\u0001\u0000\u0000\u0000\u043a\u043b\u0001\u0000\u0000\u0000\u043b\u00c7"+ + "\u0001\u0000\u0000\u0000\u043c\u043e\u0003\u00b0P\u0000\u043d\u043c\u0001"+ + "\u0000\u0000\u0000\u043e\u043f\u0001\u0000\u0000\u0000\u043f\u043d\u0001"+ + "\u0000\u0000\u0000\u043f\u0440\u0001\u0000\u0000\u0000\u0440\u0441\u0001"+ + "\u0000\u0000\u0000\u0441\u0445\u0003\u00dae\u0000\u0442\u0444\u0003\u00b0"+ + "P\u0000\u0443\u0442\u0001\u0000\u0000\u0000\u0444\u0447\u0001\u0000\u0000"+ + "\u0000\u0445\u0443\u0001\u0000\u0000\u0000\u0445\u0446\u0001\u0000\u0000"+ + "\u0000\u0446\u0467\u0001\u0000\u0000\u0000\u0447\u0445\u0001\u0000\u0000"+ + "\u0000\u0448\u044a\u0003\u00dae\u0000\u0449\u044b\u0003\u00b0P\u0000\u044a"+ + "\u0449\u0001\u0000\u0000\u0000\u044b\u044c\u0001\u0000\u0000\u0000\u044c"+ + "\u044a\u0001\u0000\u0000\u0000\u044c\u044d\u0001\u0000\u0000\u0000\u044d"+ + "\u0467\u0001\u0000\u0000\u0000\u044e\u0450\u0003\u00b0P\u0000\u044f\u044e"+ + "\u0001\u0000\u0000\u0000\u0450\u0451\u0001\u0000\u0000\u0000\u0451\u044f"+ + "\u0001\u0000\u0000\u0000\u0451\u0452\u0001\u0000\u0000\u0000\u0452\u045a"+ + "\u0001\u0000\u0000\u0000\u0453\u0457\u0003\u00dae\u0000\u0454\u0456\u0003"+ + "\u00b0P\u0000\u0455\u0454\u0001\u0000\u0000\u0000\u0456\u0459\u0001\u0000"+ + "\u0000\u0000\u0457\u0455\u0001\u0000\u0000\u0000\u0457\u0458\u0001\u0000"+ + "\u0000\u0000\u0458\u045b\u0001\u0000\u0000\u0000\u0459\u0457\u0001\u0000"+ + "\u0000\u0000\u045a\u0453\u0001\u0000\u0000\u0000\u045a\u045b\u0001\u0000"+ + "\u0000\u0000\u045b\u045c\u0001\u0000\u0000\u0000\u045c\u045d\u0003\u00b8"+ + "T\u0000\u045d\u0467\u0001\u0000\u0000\u0000\u045e\u0460\u0003\u00dae\u0000"+ + "\u045f\u0461\u0003\u00b0P\u0000\u0460\u045f\u0001\u0000\u0000\u0000\u0461"+ + "\u0462\u0001\u0000\u0000\u0000\u0462\u0460\u0001\u0000\u0000\u0000\u0462"+ + "\u0463\u0001\u0000\u0000\u0000\u0463\u0464\u0001\u0000\u0000\u0000\u0464"+ + "\u0465\u0003\u00b8T\u0000\u0465\u0467\u0001\u0000\u0000\u0000\u0466\u043d"+ + "\u0001\u0000\u0000\u0000\u0466\u0448\u0001\u0000\u0000\u0000\u0466\u044f"+ + "\u0001\u0000\u0000\u0000\u0466\u045e\u0001\u0000\u0000\u0000\u0467\u00c9"+ + "\u0001\u0000\u0000\u0000\u0468\u0469\u0007\u0004\u0000\u0000\u0469\u046a"+ + "\u0007\u0005\u0000\u0000\u046a\u046b\u0007\u0010\u0000\u0000\u046b\u00cb"+ + "\u0001\u0000\u0000\u0000\u046c\u046d\u0007\u0004\u0000\u0000\u046d\u046e"+ + "\u0007\u0011\u0000\u0000\u046e\u046f\u0007\u0002\u0000\u0000\u046f\u00cd"+ + "\u0001\u0000\u0000\u0000\u0470\u0471\u0005=\u0000\u0000\u0471\u00cf\u0001"+ + "\u0000\u0000\u0000\u0472\u0473\u0007\u001f\u0000\u0000\u0473\u0474\u0007"+ + " \u0000\u0000\u0474\u00d1\u0001\u0000\u0000\u0000\u0475\u0476\u0005:\u0000"+ + "\u0000\u0476\u0477\u0005:\u0000\u0000\u0477\u00d3\u0001\u0000\u0000\u0000"+ + "\u0478\u0479\u0005:\u0000\u0000\u0479\u00d5\u0001\u0000\u0000\u0000\u047a"+ + "\u047b\u0005,\u0000\u0000\u047b\u00d7\u0001\u0000\u0000\u0000\u047c\u047d"+ + "\u0007\u0010\u0000\u0000\u047d\u047e\u0007\u0007\u0000\u0000\u047e\u047f"+ + "\u0007\u0011\u0000\u0000\u047f\u0480\u0007\u0002\u0000\u0000\u0480\u00d9"+ + "\u0001\u0000\u0000\u0000\u0481\u0482\u0005.\u0000\u0000\u0482\u00db\u0001"+ + "\u0000\u0000\u0000\u0483\u0484\u0007\u0015\u0000\u0000\u0484\u0485\u0007"+ + "\u0004\u0000\u0000\u0485\u0486\u0007\u000e\u0000\u0000\u0486\u0487\u0007"+ + "\u0011\u0000\u0000\u0487\u0488\u0007\u0007\u0000\u0000\u0488\u00dd\u0001"+ + "\u0000\u0000\u0000\u0489\u048a\u0007\u0015\u0000\u0000\u048a\u048b\u0007"+ + "\n\u0000\u0000\u048b\u048c\u0007\f\u0000\u0000\u048c\u048d\u0007\u0011"+ + "\u0000\u0000\u048d\u048e\u0007\u000b\u0000\u0000\u048e\u00df\u0001\u0000"+ + "\u0000\u0000\u048f\u0490\u0007\n\u0000\u0000\u0490\u0491\u0007\u0005\u0000"+ + "\u0000\u0491\u00e1\u0001\u0000\u0000\u0000\u0492\u0493\u0007\n\u0000\u0000"+ + "\u0493\u0494\u0007\u0011\u0000\u0000\u0494\u00e3\u0001\u0000\u0000\u0000"+ + "\u0495\u0496\u0007\u000e\u0000\u0000\u0496\u0497\u0007\u0004\u0000\u0000"+ + "\u0497\u0498\u0007\u0011\u0000\u0000\u0498\u0499\u0007\u000b\u0000\u0000"+ + "\u0499\u00e5\u0001\u0000\u0000\u0000\u049a\u049b\u0007\u000e\u0000\u0000"+ + "\u049b\u049c\u0007\n\u0000\u0000\u049c\u049d\u0007\u0013\u0000\u0000\u049d"+ + "\u049e\u0007\u0007\u0000\u0000\u049e\u00e7\u0001\u0000\u0000\u0000\u049f"+ + "\u04a0\u0007\u0005\u0000\u0000\u04a0\u04a1\u0007\t\u0000\u0000\u04a1\u04a2"+ + "\u0007\u000b\u0000\u0000\u04a2\u00e9\u0001\u0000\u0000\u0000\u04a3\u04a4"+ + "\u0007\u0005\u0000\u0000\u04a4\u04a5\u0007\u0016\u0000\u0000\u04a5\u04a6"+ + "\u0007\u000e\u0000\u0000\u04a6\u04a7\u0007\u000e\u0000\u0000\u04a7\u00eb"+ + "\u0001\u0000\u0000\u0000\u04a8\u04a9\u0007\u0005\u0000\u0000\u04a9\u04aa"+ + "\u0007\u0016\u0000\u0000\u04aa\u04ab\u0007\u000e\u0000\u0000\u04ab\u04ac"+ + "\u0007\u000e\u0000\u0000\u04ac\u04ad\u0007\u0011\u0000\u0000\u04ad\u00ed"+ + "\u0001\u0000\u0000\u0000\u04ae\u04af\u0007\t\u0000\u0000\u04af\u04b0\u0007"+ + "\u0005\u0000\u0000\u04b0\u00ef\u0001\u0000\u0000\u0000\u04b1\u04b2\u0007"+ + "\t\u0000\u0000\u04b2\u04b3\u0007\f\u0000\u0000\u04b3\u00f1\u0001\u0000"+ + "\u0000\u0000\u04b4\u04b5\u0005?\u0000\u0000\u04b5\u00f3\u0001\u0000\u0000"+ + "\u0000\u04b6\u04b7\u0007\f\u0000\u0000\u04b7\u04b8\u0007\u000e\u0000\u0000"+ + "\u04b8\u04b9\u0007\n\u0000\u0000\u04b9\u04ba\u0007\u0013\u0000\u0000\u04ba"+ + "\u04bb\u0007\u0007\u0000\u0000\u04bb\u00f5\u0001\u0000\u0000\u0000\u04bc"+ + "\u04bd\u0007\u000b\u0000\u0000\u04bd\u04be\u0007\f\u0000\u0000\u04be\u04bf"+ + "\u0007\u0016\u0000\u0000\u04bf\u04c0\u0007\u0007\u0000\u0000\u04c0\u00f7"+ + "\u0001\u0000\u0000\u0000\u04c1\u04c2\u0007\u0014\u0000\u0000\u04c2\u04c3"+ + "\u0007\n\u0000\u0000\u04c3\u04c4\u0007\u000b\u0000\u0000\u04c4\u04c5\u0007"+ + "\u0003\u0000\u0000\u04c5\u00f9\u0001\u0000\u0000\u0000\u04c6\u04c7\u0005"+ + "=\u0000\u0000\u04c7\u04c8\u0005=\u0000\u0000\u04c8\u00fb\u0001\u0000\u0000"+ + "\u0000\u04c9\u04ca\u0005=\u0000\u0000\u04ca\u04cb\u0005~\u0000\u0000\u04cb"+ + "\u00fd\u0001\u0000\u0000\u0000\u04cc\u04cd\u0005!\u0000\u0000\u04cd\u04ce"+ + "\u0005=\u0000\u0000\u04ce\u00ff\u0001\u0000\u0000\u0000\u04cf\u04d0\u0005"+ + "<\u0000\u0000\u04d0\u0101\u0001\u0000\u0000\u0000\u04d1\u04d2\u0005<\u0000"+ + "\u0000\u04d2\u04d3\u0005=\u0000\u0000\u04d3\u0103\u0001\u0000\u0000\u0000"+ + "\u04d4\u04d5\u0005>\u0000\u0000\u04d5\u0105\u0001\u0000\u0000\u0000\u04d6"+ + "\u04d7\u0005>\u0000\u0000\u04d7\u04d8\u0005=\u0000\u0000\u04d8\u0107\u0001"+ + "\u0000\u0000\u0000\u04d9\u04da\u0005+\u0000\u0000\u04da\u0109\u0001\u0000"+ + "\u0000\u0000\u04db\u04dc\u0005-\u0000\u0000\u04dc\u010b\u0001\u0000\u0000"+ + "\u0000\u04dd\u04de\u0005*\u0000\u0000\u04de\u010d\u0001\u0000\u0000\u0000"+ + "\u04df\u04e0\u0005/\u0000\u0000\u04e0\u010f\u0001\u0000\u0000\u0000\u04e1"+ + "\u04e2\u0005%\u0000\u0000\u04e2\u0111\u0001\u0000\u0000\u0000\u04e3\u04e4"+ + "\u0005{\u0000\u0000\u04e4\u0113\u0001\u0000\u0000\u0000\u04e5\u04e6\u0005"+ + "}\u0000\u0000\u04e6\u0115\u0001\u0000\u0000\u0000\u04e7\u04e8\u0005?\u0000"+ + "\u0000\u04e8\u04e9\u0005?\u0000\u0000\u04e9\u0117\u0001\u0000\u0000\u0000"+ + "\u04ea\u04eb\u0003,\u000e\u0000\u04eb\u04ec\u0001\u0000\u0000\u0000\u04ec"+ + "\u04ed\u0006\u0084#\u0000\u04ed\u0119\u0001\u0000\u0000\u0000\u04ee\u04f1"+ + "\u0003\u00f2q\u0000\u04ef\u04f2\u0003\u00b2Q\u0000\u04f0\u04f2\u0003\u00c0"+ + "X\u0000\u04f1\u04ef\u0001\u0000\u0000\u0000\u04f1\u04f0\u0001\u0000\u0000"+ + "\u0000\u04f2\u04f6\u0001\u0000\u0000\u0000\u04f3\u04f5\u0003\u00c2Y\u0000"+ + "\u04f4\u04f3\u0001\u0000\u0000\u0000\u04f5\u04f8\u0001\u0000\u0000\u0000"+ + "\u04f6\u04f4\u0001\u0000\u0000\u0000\u04f6\u04f7\u0001\u0000\u0000\u0000"+ + "\u04f7\u0500\u0001\u0000\u0000\u0000\u04f8\u04f6\u0001\u0000\u0000\u0000"+ + "\u04f9\u04fb\u0003\u00f2q\u0000\u04fa\u04fc\u0003\u00b0P\u0000\u04fb\u04fa"+ + "\u0001\u0000\u0000\u0000\u04fc\u04fd\u0001\u0000\u0000\u0000\u04fd\u04fb"+ + "\u0001\u0000\u0000\u0000\u04fd\u04fe\u0001\u0000\u0000\u0000\u04fe\u0500"+ + "\u0001\u0000\u0000\u0000\u04ff\u04ee\u0001\u0000\u0000\u0000\u04ff\u04f9"+ + "\u0001\u0000\u0000\u0000\u0500\u011b\u0001\u0000\u0000\u0000\u0501\u0504"+ + "\u0003\u0116\u0083\u0000\u0502\u0505\u0003\u00b2Q\u0000\u0503\u0505\u0003"+ + "\u00c0X\u0000\u0504\u0502\u0001\u0000\u0000\u0000\u0504\u0503\u0001\u0000"+ + "\u0000\u0000\u0505\u0509\u0001\u0000\u0000\u0000\u0506\u0508\u0003\u00c2"+ + "Y\u0000\u0507\u0506\u0001\u0000\u0000\u0000\u0508\u050b\u0001\u0000\u0000"+ + "\u0000\u0509\u0507\u0001\u0000\u0000\u0000\u0509\u050a\u0001\u0000\u0000"+ + "\u0000\u050a\u0513\u0001\u0000\u0000\u0000\u050b\u0509\u0001\u0000\u0000"+ + "\u0000\u050c\u050e\u0003\u0116\u0083\u0000\u050d\u050f\u0003\u00b0P\u0000"+ + "\u050e\u050d\u0001\u0000\u0000\u0000\u050f\u0510\u0001\u0000\u0000\u0000"+ + "\u0510\u050e\u0001\u0000\u0000\u0000\u0510\u0511\u0001\u0000\u0000\u0000"+ + "\u0511\u0513\u0001\u0000\u0000\u0000\u0512\u0501\u0001\u0000\u0000\u0000"+ + "\u0512\u050c\u0001\u0000\u0000\u0000\u0513\u011d\u0001\u0000\u0000\u0000"+ + "\u0514\u0515\u0005[\u0000\u0000\u0515\u0516\u0001\u0000\u0000\u0000\u0516"+ + "\u0517\u0006\u0087\u0004\u0000\u0517\u0518\u0006\u0087\u0004\u0000\u0518"+ + "\u011f\u0001\u0000\u0000\u0000\u0519\u051a\u0005]\u0000\u0000\u051a\u051b"+ + "\u0001\u0000\u0000\u0000\u051b\u051c\u0006\u0088\u000e\u0000\u051c\u051d"+ + "\u0006\u0088\u000e\u0000\u051d\u0121\u0001\u0000\u0000\u0000\u051e\u051f"+ + "\u0005(\u0000\u0000\u051f\u0520\u0001\u0000\u0000\u0000\u0520\u0521\u0006"+ + "\u0089\u0004\u0000\u0521\u0522\u0006\u0089\u0004\u0000\u0522\u0123\u0001"+ + "\u0000\u0000\u0000\u0523\u0524\u0005)\u0000\u0000\u0524\u0525\u0001\u0000"+ + "\u0000\u0000\u0525\u0526\u0006\u008a\u000e\u0000\u0526\u0527\u0006\u008a"+ + "\u000e\u0000\u0527\u0125\u0001\u0000\u0000\u0000\u0528\u052c\u0003\u00b2"+ + "Q\u0000\u0529\u052b\u0003\u00c2Y\u0000\u052a\u0529\u0001\u0000\u0000\u0000"+ + "\u052b\u052e\u0001\u0000\u0000\u0000\u052c\u052a\u0001\u0000\u0000\u0000"+ + "\u052c\u052d\u0001\u0000\u0000\u0000\u052d\u0539\u0001\u0000\u0000\u0000"+ + "\u052e\u052c\u0001\u0000\u0000\u0000\u052f\u0532\u0003\u00c0X\u0000\u0530"+ + "\u0532\u0003\u00baU\u0000\u0531\u052f\u0001\u0000\u0000\u0000\u0531\u0530"+ + "\u0001\u0000\u0000\u0000\u0532\u0534\u0001\u0000\u0000\u0000\u0533\u0535"+ + "\u0003\u00c2Y\u0000\u0534\u0533\u0001\u0000\u0000\u0000\u0535\u0536\u0001"+ + "\u0000\u0000\u0000\u0536\u0534\u0001\u0000\u0000\u0000\u0536\u0537\u0001"+ + "\u0000\u0000\u0000\u0537\u0539\u0001\u0000\u0000\u0000\u0538\u0528\u0001"+ + "\u0000\u0000\u0000\u0538\u0531\u0001\u0000\u0000\u0000\u0539\u0127\u0001"+ + "\u0000\u0000\u0000\u053a\u053c\u0003\u00bcV\u0000\u053b\u053d\u0003\u00be"+ + "W\u0000\u053c\u053b\u0001\u0000\u0000\u0000\u053d\u053e\u0001\u0000\u0000"+ + "\u0000\u053e\u053c\u0001\u0000\u0000\u0000\u053e\u053f\u0001\u0000\u0000"+ + "\u0000\u053f\u0540\u0001\u0000\u0000\u0000\u0540\u0541\u0003\u00bcV\u0000"+ + "\u0541\u0129\u0001\u0000\u0000\u0000\u0542\u0543\u0003\u0128\u008c\u0000"+ + "\u0543\u012b\u0001\u0000\u0000\u0000\u0544\u0545\u0003\u0010\u0000\u0000"+ + "\u0545\u0546\u0001\u0000\u0000\u0000\u0546\u0547\u0006\u008e\u0000\u0000"+ + "\u0547\u012d\u0001\u0000\u0000\u0000\u0548\u0549\u0003\u0012\u0001\u0000"+ + "\u0549\u054a\u0001\u0000\u0000\u0000\u054a\u054b\u0006\u008f\u0000\u0000"+ + "\u054b\u012f\u0001\u0000\u0000\u0000\u054c\u054d\u0003\u0014\u0002\u0000"+ + "\u054d\u054e\u0001\u0000\u0000\u0000\u054e\u054f\u0006\u0090\u0000\u0000"+ + "\u054f\u0131\u0001\u0000\u0000\u0000\u0550\u0551\u0003\u00aeO\u0000\u0551"+ + "\u0552\u0001\u0000\u0000\u0000\u0552\u0553\u0006\u0091\r\u0000\u0553\u0554"+ + "\u0006\u0091\u000e\u0000\u0554\u0133\u0001\u0000\u0000\u0000\u0555\u0556"+ + "\u0003\u011e\u0087\u0000\u0556\u0557\u0001\u0000\u0000\u0000\u0557\u0558"+ + "\u0006\u0092\u0015\u0000\u0558\u0135\u0001\u0000\u0000\u0000\u0559\u055a"+ + "\u0003\u0120\u0088\u0000\u055a\u055b\u0001\u0000\u0000\u0000\u055b\u055c"+ + "\u0006\u0093 \u0000\u055c\u0137\u0001\u0000\u0000\u0000\u055d\u055e\u0003"+ + "\u00d4b\u0000\u055e\u055f\u0001\u0000\u0000\u0000\u055f\u0560\u0006\u0094"+ + "!\u0000\u0560\u0139\u0001\u0000\u0000\u0000\u0561\u0562\u0003\u00d2a\u0000"+ + "\u0562\u0563\u0001\u0000\u0000\u0000\u0563\u0564\u0006\u0095$\u0000\u0564"+ + "\u013b\u0001\u0000\u0000\u0000\u0565\u0566\u0003\u00d6c\u0000\u0566\u0567"+ + "\u0001\u0000\u0000\u0000\u0567\u0568\u0006\u0096\u0012\u0000\u0568\u013d"+ + "\u0001\u0000\u0000\u0000\u0569\u056a\u0003\u00ce_\u0000\u056a\u056b\u0001"+ + "\u0000\u0000\u0000\u056b\u056c\u0006\u0097\u001a\u0000\u056c\u013f\u0001"+ + "\u0000\u0000\u0000\u056d\u056e\u0007\u000f\u0000\u0000\u056e\u056f\u0007"+ + "\u0007\u0000\u0000\u056f\u0570\u0007\u000b\u0000\u0000\u0570\u0571\u0007"+ + "\u0004\u0000\u0000\u0571\u0572\u0007\u0010\u0000\u0000\u0572\u0573\u0007"+ + "\u0004\u0000\u0000\u0573\u0574\u0007\u000b\u0000\u0000\u0574\u0575\u0007"+ + "\u0004\u0000\u0000\u0575\u0141\u0001\u0000\u0000\u0000\u0576\u057a\b!"+ + "\u0000\u0000\u0577\u0578\u0005/\u0000\u0000\u0578\u057a\b\"\u0000\u0000"+ + "\u0579\u0576\u0001\u0000\u0000\u0000\u0579\u0577\u0001\u0000\u0000\u0000"+ + "\u057a\u0143\u0001\u0000\u0000\u0000\u057b\u057d\u0003\u0142\u0099\u0000"+ + "\u057c\u057b\u0001\u0000\u0000\u0000\u057d\u057e\u0001\u0000\u0000\u0000"+ + "\u057e\u057c\u0001\u0000\u0000\u0000\u057e\u057f\u0001\u0000\u0000\u0000"+ + "\u057f\u0145\u0001\u0000\u0000\u0000\u0580\u0581\u0003\u0144\u009a\u0000"+ + "\u0581\u0582\u0001\u0000\u0000\u0000\u0582\u0583\u0006\u009b%\u0000\u0583"+ + "\u0147\u0001\u0000\u0000\u0000\u0584\u0585\u0003\u00c4Z\u0000\u0585\u0586"+ + "\u0001\u0000\u0000\u0000\u0586\u0587\u0006\u009c&\u0000\u0587\u0149\u0001"+ + "\u0000\u0000\u0000\u0588\u0589\u0003\u0010\u0000\u0000\u0589\u058a\u0001"+ + "\u0000\u0000\u0000\u058a\u058b\u0006\u009d\u0000\u0000\u058b\u014b\u0001"+ + "\u0000\u0000\u0000\u058c\u058d\u0003\u0012\u0001\u0000\u058d\u058e\u0001"+ + "\u0000\u0000\u0000\u058e\u058f\u0006\u009e\u0000\u0000\u058f\u014d\u0001"+ + "\u0000\u0000\u0000\u0590\u0591\u0003\u0014\u0002\u0000\u0591\u0592\u0001"+ + "\u0000\u0000\u0000\u0592\u0593\u0006\u009f\u0000\u0000\u0593\u014f\u0001"+ + "\u0000\u0000\u0000\u0594\u0595\u0003\u0122\u0089\u0000\u0595\u0596\u0001"+ + "\u0000\u0000\u0000\u0596\u0597\u0006\u00a0\'\u0000\u0597\u0598\u0006\u00a0"+ + "\"\u0000\u0598\u0151\u0001\u0000\u0000\u0000\u0599\u059a\u0003\u00aeO"+ + "\u0000\u059a\u059b\u0001\u0000\u0000\u0000\u059b\u059c\u0006\u00a1\r\u0000"+ + "\u059c\u059d\u0006\u00a1\u000e\u0000\u059d\u0153\u0001\u0000\u0000\u0000"+ + "\u059e\u059f\u0003\u0014\u0002\u0000\u059f\u05a0\u0001\u0000\u0000\u0000"+ + "\u05a0\u05a1\u0006\u00a2\u0000\u0000\u05a1\u0155\u0001\u0000\u0000\u0000"+ + "\u05a2\u05a3\u0003\u0010\u0000\u0000\u05a3\u05a4\u0001\u0000\u0000\u0000"+ + "\u05a4\u05a5\u0006\u00a3\u0000\u0000\u05a5\u0157\u0001\u0000\u0000\u0000"+ + "\u05a6\u05a7\u0003\u0012\u0001\u0000\u05a7\u05a8\u0001\u0000\u0000\u0000"+ + "\u05a8\u05a9\u0006\u00a4\u0000\u0000\u05a9\u0159\u0001\u0000\u0000\u0000"+ + "\u05aa\u05ab\u0003\u00aeO\u0000\u05ab\u05ac\u0001\u0000\u0000\u0000\u05ac"+ + "\u05ad\u0006\u00a5\r\u0000\u05ad\u05ae\u0006\u00a5\u000e\u0000\u05ae\u015b"+ + "\u0001\u0000\u0000\u0000\u05af\u05b0\u0007#\u0000\u0000\u05b0\u05b1\u0007"+ + "\t\u0000\u0000\u05b1\u05b2\u0007\n\u0000\u0000\u05b2\u05b3\u0007\u0005"+ + "\u0000\u0000\u05b3\u015d\u0001\u0000\u0000\u0000\u05b4\u05b5\u0003\u01d6"+ + "\u00e3\u0000\u05b5\u05b6\u0001\u0000\u0000\u0000\u05b6\u05b7\u0006\u00a7"+ + "\u0010\u0000\u05b7\u015f\u0001\u0000\u0000\u0000\u05b8\u05b9\u0003\u00ee"+ + "o\u0000\u05b9\u05ba\u0001\u0000\u0000\u0000\u05ba\u05bb\u0006\u00a8\u000f"+ + "\u0000\u05bb\u05bc\u0006\u00a8\u000e\u0000\u05bc\u05bd\u0006\u00a8\u0004"+ + "\u0000\u05bd\u0161\u0001\u0000\u0000\u0000\u05be\u05bf\u0007\u0016\u0000"+ + "\u0000\u05bf\u05c0\u0007\u0011\u0000\u0000\u05c0\u05c1\u0007\n\u0000\u0000"+ + "\u05c1\u05c2\u0007\u0005\u0000\u0000\u05c2\u05c3\u0007\u0006\u0000\u0000"+ + "\u05c3\u05c4\u0001\u0000\u0000\u0000\u05c4\u05c5\u0006\u00a9\u000e\u0000"+ + "\u05c5\u05c6\u0006\u00a9\u0004\u0000\u05c6\u0163\u0001\u0000\u0000\u0000"+ + "\u05c7\u05c8\u0003\u0144\u009a\u0000\u05c8\u05c9\u0001\u0000\u0000\u0000"+ + "\u05c9\u05ca\u0006\u00aa%\u0000\u05ca\u0165\u0001\u0000\u0000\u0000\u05cb"+ + "\u05cc\u0003\u00c4Z\u0000\u05cc\u05cd\u0001\u0000\u0000\u0000\u05cd\u05ce"+ + "\u0006\u00ab&\u0000\u05ce\u0167\u0001\u0000\u0000\u0000\u05cf\u05d0\u0003"+ + "\u00d4b\u0000\u05d0\u05d1\u0001\u0000\u0000\u0000\u05d1\u05d2\u0006\u00ac"+ + "!\u0000\u05d2\u0169\u0001\u0000\u0000\u0000\u05d3\u05d4\u0003\u0126\u008b"+ + "\u0000\u05d4\u05d5\u0001\u0000\u0000\u0000\u05d5\u05d6\u0006\u00ad\u0014"+ + "\u0000\u05d6\u016b\u0001\u0000\u0000\u0000\u05d7\u05d8\u0003\u012a\u008d"+ + "\u0000\u05d8\u05d9\u0001\u0000\u0000\u0000\u05d9\u05da\u0006\u00ae\u0013"+ + "\u0000\u05da\u016d\u0001\u0000\u0000\u0000\u05db\u05dc\u0003\u0010\u0000"+ + "\u0000\u05dc\u05dd\u0001\u0000\u0000\u0000\u05dd\u05de\u0006\u00af\u0000"+ + "\u0000\u05de\u016f\u0001\u0000\u0000\u0000\u05df\u05e0\u0003\u0012\u0001"+ + "\u0000\u05e0\u05e1\u0001\u0000\u0000\u0000\u05e1\u05e2\u0006\u00b0\u0000"+ + "\u0000\u05e2\u0171\u0001\u0000\u0000\u0000\u05e3\u05e4\u0003\u0014\u0002"+ + "\u0000\u05e4\u05e5\u0001\u0000\u0000\u0000\u05e5\u05e6\u0006\u00b1\u0000"+ + "\u0000\u05e6\u0173\u0001\u0000\u0000\u0000\u05e7\u05e8\u0003\u00aeO\u0000"+ + "\u05e8\u05e9\u0001\u0000\u0000\u0000\u05e9\u05ea\u0006\u00b2\r\u0000\u05ea"+ + "\u05eb\u0006\u00b2\u000e\u0000\u05eb\u0175\u0001\u0000\u0000\u0000\u05ec"+ + "\u05ed\u0003\u00d4b\u0000\u05ed\u05ee\u0001\u0000\u0000\u0000\u05ee\u05ef"+ + "\u0006\u00b3!\u0000\u05ef\u0177\u0001\u0000\u0000\u0000\u05f0\u05f1\u0003"+ + "\u00d6c\u0000\u05f1\u05f2\u0001\u0000\u0000\u0000\u05f2\u05f3\u0006\u00b4"+ + "\u0012\u0000\u05f3\u0179\u0001\u0000\u0000\u0000\u05f4\u05f5\u0003\u00da"+ + "e\u0000\u05f5\u05f6\u0001\u0000\u0000\u0000\u05f6\u05f7\u0006\u00b5\u0011"+ + "\u0000\u05f7\u017b\u0001\u0000\u0000\u0000\u05f8\u05f9\u0003\u00eeo\u0000"+ + "\u05f9\u05fa\u0001\u0000\u0000\u0000\u05fa\u05fb\u0006\u00b6\u000f\u0000"+ + "\u05fb\u05fc\u0006\u00b6(\u0000\u05fc\u017d\u0001\u0000\u0000\u0000\u05fd"+ + "\u05fe\u0003\u0144\u009a\u0000\u05fe\u05ff\u0001\u0000\u0000\u0000\u05ff"+ + "\u0600\u0006\u00b7%\u0000\u0600\u017f\u0001\u0000\u0000\u0000\u0601\u0602"+ + "\u0003\u00c4Z\u0000\u0602\u0603\u0001\u0000\u0000\u0000\u0603\u0604\u0006"+ + "\u00b8&\u0000\u0604\u0181\u0001\u0000\u0000\u0000\u0605\u0606\u0003\u0010"+ + "\u0000\u0000\u0606\u0607\u0001\u0000\u0000\u0000\u0607\u0608\u0006\u00b9"+ + "\u0000\u0000\u0608\u0183\u0001\u0000\u0000\u0000\u0609\u060a\u0003\u0012"+ + "\u0001\u0000\u060a\u060b\u0001\u0000\u0000\u0000\u060b\u060c\u0006\u00ba"+ + "\u0000\u0000\u060c\u0185\u0001\u0000\u0000\u0000\u060d\u060e\u0003\u0014"+ + "\u0002\u0000\u060e\u060f\u0001\u0000\u0000\u0000\u060f\u0610\u0006\u00bb"+ + "\u0000\u0000\u0610\u0187\u0001\u0000\u0000\u0000\u0611\u0612\u0003\u00ae"+ + "O\u0000\u0612\u0613\u0001\u0000\u0000\u0000\u0613\u0614\u0006\u00bc\r"+ + "\u0000\u0614\u0615\u0006\u00bc\u000e\u0000\u0615\u0616\u0006\u00bc\u000e"+ + "\u0000\u0616\u0189\u0001\u0000\u0000\u0000\u0617\u0618\u0003\u00d6c\u0000"+ + "\u0618\u0619\u0001\u0000\u0000\u0000\u0619\u061a\u0006\u00bd\u0012\u0000"+ + "\u061a\u018b\u0001\u0000\u0000\u0000\u061b\u061c\u0003\u00dae\u0000\u061c"+ + "\u061d\u0001\u0000\u0000\u0000\u061d\u061e\u0006\u00be\u0011\u0000\u061e"+ + "\u018d\u0001\u0000\u0000\u0000\u061f\u0620\u0003\u01be\u00d7\u0000\u0620"+ + "\u0621\u0001\u0000\u0000\u0000\u0621\u0622\u0006\u00bf\u001b\u0000\u0622"+ + "\u018f\u0001\u0000\u0000\u0000\u0623\u0624\u0003\u0010\u0000\u0000\u0624"+ + "\u0625\u0001\u0000\u0000\u0000\u0625\u0626\u0006\u00c0\u0000\u0000\u0626"+ + "\u0191\u0001\u0000\u0000\u0000\u0627\u0628\u0003\u0012\u0001\u0000\u0628"+ + "\u0629\u0001\u0000\u0000\u0000\u0629\u062a\u0006\u00c1\u0000\u0000\u062a"+ + "\u0193\u0001\u0000\u0000\u0000\u062b\u062c\u0003\u0014\u0002\u0000\u062c"+ + "\u062d\u0001\u0000\u0000\u0000\u062d\u062e\u0006\u00c2\u0000\u0000\u062e"+ + "\u0195\u0001\u0000\u0000\u0000\u062f\u0630\u0003\u00aeO\u0000\u0630\u0631"+ + "\u0001\u0000\u0000\u0000\u0631\u0632\u0006\u00c3\r\u0000\u0632\u0633\u0006"+ + "\u00c3\u000e\u0000\u0633\u0197\u0001\u0000\u0000\u0000\u0634\u0635\u0003"+ + "\u00dae\u0000\u0635\u0636\u0001\u0000\u0000\u0000\u0636\u0637\u0006\u00c4"+ + "\u0011\u0000\u0637\u0199\u0001\u0000\u0000\u0000\u0638\u0639\u0003\u00f2"+ + "q\u0000\u0639\u063a\u0001\u0000\u0000\u0000\u063a\u063b\u0006\u00c5\u001c"+ + "\u0000\u063b\u019b\u0001\u0000\u0000\u0000\u063c\u063d\u0003\u011a\u0085"+ + "\u0000\u063d\u063e\u0001\u0000\u0000\u0000\u063e\u063f\u0006\u00c6\u001d"+ + "\u0000\u063f\u019d\u0001\u0000\u0000\u0000\u0640\u0641\u0003\u0116\u0083"+ + "\u0000\u0641\u0642\u0001\u0000\u0000\u0000\u0642\u0643\u0006\u00c7\u001e"+ + "\u0000\u0643\u019f\u0001\u0000\u0000\u0000\u0644\u0645\u0003\u011c\u0086"+ + "\u0000\u0645\u0646\u0001\u0000\u0000\u0000\u0646\u0647\u0006\u00c8\u001f"+ + "\u0000\u0647\u01a1\u0001\u0000\u0000\u0000\u0648\u0649\u0003\u012a\u008d"+ + "\u0000\u0649\u064a\u0001\u0000\u0000\u0000\u064a\u064b\u0006\u00c9\u0013"+ + "\u0000\u064b\u01a3\u0001\u0000\u0000\u0000\u064c\u064d\u0003\u0126\u008b"+ + "\u0000\u064d\u064e\u0001\u0000\u0000\u0000\u064e\u064f\u0006\u00ca\u0014"+ + "\u0000\u064f\u01a5\u0001\u0000\u0000\u0000\u0650\u0651\u0003\u0010\u0000"+ + "\u0000\u0651\u0652\u0001\u0000\u0000\u0000\u0652\u0653\u0006\u00cb\u0000"+ + "\u0000\u0653\u01a7\u0001\u0000\u0000\u0000\u0654\u0655\u0003\u0012\u0001"+ + "\u0000\u0655\u0656\u0001\u0000\u0000\u0000\u0656\u0657\u0006\u00cc\u0000"+ + "\u0000\u0657\u01a9\u0001\u0000\u0000\u0000\u0658\u0659\u0003\u0014\u0002"+ + "\u0000\u0659\u065a\u0001\u0000\u0000\u0000\u065a\u065b\u0006\u00cd\u0000"+ + "\u0000\u065b\u01ab\u0001\u0000\u0000\u0000\u065c\u065d\u0003\u00aeO\u0000"+ + "\u065d\u065e\u0001\u0000\u0000\u0000\u065e\u065f\u0006\u00ce\r\u0000\u065f"+ + "\u0660\u0006\u00ce\u000e\u0000\u0660\u01ad\u0001\u0000\u0000\u0000\u0661"+ + "\u0662\u0003\u00dae\u0000\u0662\u0663\u0001\u0000\u0000\u0000\u0663\u0664"+ + "\u0006\u00cf\u0011\u0000\u0664\u01af\u0001\u0000\u0000\u0000\u0665\u0666"+ + "\u0003\u00d6c\u0000\u0666\u0667\u0001\u0000\u0000\u0000\u0667\u0668\u0006"+ + "\u00d0\u0012\u0000\u0668\u01b1\u0001\u0000\u0000\u0000\u0669\u066a\u0003"+ + "\u00f2q\u0000\u066a\u066b\u0001\u0000\u0000\u0000\u066b\u066c\u0006\u00d1"+ + "\u001c\u0000\u066c\u01b3\u0001\u0000\u0000\u0000\u066d\u066e\u0003\u011a"+ + "\u0085\u0000\u066e\u066f\u0001\u0000\u0000\u0000\u066f\u0670\u0006\u00d2"+ + "\u001d\u0000\u0670\u01b5\u0001\u0000\u0000\u0000\u0671\u0672\u0003\u0116"+ + "\u0083\u0000\u0672\u0673\u0001\u0000\u0000\u0000\u0673\u0674\u0006\u00d3"+ + "\u001e\u0000\u0674\u01b7\u0001\u0000\u0000\u0000\u0675\u0676\u0003\u011c"+ + "\u0086\u0000\u0676\u0677\u0001\u0000\u0000\u0000\u0677\u0678\u0006\u00d4"+ + "\u001f\u0000\u0678\u01b9\u0001\u0000\u0000\u0000\u0679\u067e\u0003\u00b2"+ + "Q\u0000\u067a\u067e\u0003\u00b0P\u0000\u067b\u067e\u0003\u00c0X\u0000"+ + "\u067c\u067e\u0003\u010c~\u0000\u067d\u0679\u0001\u0000\u0000\u0000\u067d"+ + "\u067a\u0001\u0000\u0000\u0000\u067d\u067b\u0001\u0000\u0000\u0000\u067d"+ + "\u067c\u0001\u0000\u0000\u0000\u067e\u01bb\u0001\u0000\u0000\u0000\u067f"+ + "\u0682\u0003\u00b2Q\u0000\u0680\u0682\u0003\u010c~\u0000\u0681\u067f\u0001"+ + "\u0000\u0000\u0000\u0681\u0680\u0001\u0000\u0000\u0000\u0682\u0686\u0001"+ + "\u0000\u0000\u0000\u0683\u0685\u0003\u01ba\u00d5\u0000\u0684\u0683\u0001"+ + "\u0000\u0000\u0000\u0685\u0688\u0001\u0000\u0000\u0000\u0686\u0684\u0001"+ + "\u0000\u0000\u0000\u0686\u0687\u0001\u0000\u0000\u0000\u0687\u0693\u0001"+ + "\u0000\u0000\u0000\u0688\u0686\u0001\u0000\u0000\u0000\u0689\u068c\u0003"+ + "\u00c0X\u0000\u068a\u068c\u0003\u00baU\u0000\u068b\u0689\u0001\u0000\u0000"+ + "\u0000\u068b\u068a\u0001\u0000\u0000\u0000\u068c\u068e\u0001\u0000\u0000"+ + "\u0000\u068d\u068f\u0003\u01ba\u00d5\u0000\u068e\u068d\u0001\u0000\u0000"+ + "\u0000\u068f\u0690\u0001\u0000\u0000\u0000\u0690\u068e\u0001\u0000\u0000"+ + "\u0000\u0690\u0691\u0001\u0000\u0000\u0000\u0691\u0693\u0001\u0000\u0000"+ + "\u0000\u0692\u0681\u0001\u0000\u0000\u0000\u0692\u068b\u0001\u0000\u0000"+ + "\u0000\u0693\u01bd\u0001\u0000\u0000\u0000\u0694\u0697\u0003\u01bc\u00d6"+ + "\u0000\u0695\u0697\u0003\u0128\u008c\u0000\u0696\u0694\u0001\u0000\u0000"+ + "\u0000\u0696\u0695\u0001\u0000\u0000\u0000\u0697\u0698\u0001\u0000\u0000"+ + "\u0000\u0698\u0696\u0001\u0000\u0000\u0000\u0698\u0699\u0001\u0000\u0000"+ + "\u0000\u0699\u01bf\u0001\u0000\u0000\u0000\u069a\u069b\u0003\u0010\u0000"+ + "\u0000\u069b\u069c\u0001\u0000\u0000\u0000\u069c\u069d\u0006\u00d8\u0000"+ + "\u0000\u069d\u01c1\u0001\u0000\u0000\u0000\u069e\u069f\u0003\u0012\u0001"+ + "\u0000\u069f\u06a0\u0001\u0000\u0000\u0000\u06a0\u06a1\u0006\u00d9\u0000"+ + "\u0000\u06a1\u01c3\u0001\u0000\u0000\u0000\u06a2\u06a3\u0003\u0014\u0002"+ + "\u0000\u06a3\u06a4\u0001\u0000\u0000\u0000\u06a4\u06a5\u0006\u00da\u0000"+ + "\u0000\u06a5\u01c5\u0001\u0000\u0000\u0000\u06a6\u06a7\u0003\u00aeO\u0000"+ + "\u06a7\u06a8\u0001\u0000\u0000\u0000\u06a8\u06a9\u0006\u00db\r\u0000\u06a9"+ + "\u06aa\u0006\u00db\u000e\u0000\u06aa\u01c7\u0001\u0000\u0000\u0000\u06ab"+ + "\u06ac\u0003\u00ce_\u0000\u06ac\u06ad\u0001\u0000\u0000\u0000\u06ad\u06ae"+ + "\u0006\u00dc\u001a\u0000\u06ae\u01c9\u0001\u0000\u0000\u0000\u06af\u06b0"+ + "\u0003\u00d6c\u0000\u06b0\u06b1\u0001\u0000\u0000\u0000\u06b1\u06b2\u0006"+ + "\u00dd\u0012\u0000\u06b2\u01cb\u0001\u0000\u0000\u0000\u06b3\u06b4\u0003"+ + "\u00dae\u0000\u06b4\u06b5\u0001\u0000\u0000\u0000\u06b5\u06b6\u0006\u00de"+ + "\u0011\u0000\u06b6\u01cd\u0001\u0000\u0000\u0000\u06b7\u06b8\u0003\u00f2"+ + "q\u0000\u06b8\u06b9\u0001\u0000\u0000\u0000\u06b9\u06ba\u0006\u00df\u001c"+ + "\u0000\u06ba\u01cf\u0001\u0000\u0000\u0000\u06bb\u06bc\u0003\u011a\u0085"+ + "\u0000\u06bc\u06bd\u0001\u0000\u0000\u0000\u06bd\u06be\u0006\u00e0\u001d"+ + "\u0000\u06be\u01d1\u0001\u0000\u0000\u0000\u06bf\u06c0\u0003\u0116\u0083"+ + "\u0000\u06c0\u06c1\u0001\u0000\u0000\u0000\u06c1\u06c2\u0006\u00e1\u001e"+ + "\u0000\u06c2\u01d3\u0001\u0000\u0000\u0000\u06c3\u06c4\u0003\u011c\u0086"+ + "\u0000\u06c4\u06c5\u0001\u0000\u0000\u0000\u06c5\u06c6\u0006\u00e2\u001f"+ + "\u0000\u06c6\u01d5\u0001\u0000\u0000\u0000\u06c7\u06c8\u0007\u0004\u0000"+ + "\u0000\u06c8\u06c9\u0007\u0011\u0000\u0000\u06c9\u01d7\u0001\u0000\u0000"+ + "\u0000\u06ca\u06cb\u0003\u01be\u00d7\u0000\u06cb\u06cc\u0001\u0000\u0000"+ + "\u0000\u06cc\u06cd\u0006\u00e4\u001b\u0000\u06cd\u01d9\u0001\u0000\u0000"+ + "\u0000\u06ce\u06cf\u0003\u0010\u0000\u0000\u06cf\u06d0\u0001\u0000\u0000"+ + "\u0000\u06d0\u06d1\u0006\u00e5\u0000\u0000\u06d1\u01db\u0001\u0000\u0000"+ + "\u0000\u06d2\u06d3\u0003\u0012\u0001\u0000\u06d3\u06d4\u0001\u0000\u0000"+ + "\u0000\u06d4\u06d5\u0006\u00e6\u0000\u0000\u06d5\u01dd\u0001\u0000\u0000"+ + "\u0000\u06d6\u06d7\u0003\u0014\u0002\u0000\u06d7\u06d8\u0001\u0000\u0000"+ + "\u0000\u06d8\u06d9\u0006\u00e7\u0000\u0000\u06d9\u01df\u0001\u0000\u0000"+ + "\u0000\u06da\u06db\u0003\u00aeO\u0000\u06db\u06dc\u0001\u0000\u0000\u0000"+ + "\u06dc\u06dd\u0006\u00e8\r\u0000\u06dd\u06de\u0006\u00e8\u000e\u0000\u06de"+ + "\u01e1\u0001\u0000\u0000\u0000\u06df\u06e0\u0007\n\u0000\u0000\u06e0\u06e1"+ + "\u0007\u0005\u0000\u0000\u06e1\u06e2\u0007\u0015\u0000\u0000\u06e2\u06e3"+ + "\u0007\t\u0000\u0000\u06e3\u01e3\u0001\u0000\u0000\u0000\u06e4\u06e5\u0003"+ + "\u0010\u0000\u0000\u06e5\u06e6\u0001\u0000\u0000\u0000\u06e6\u06e7\u0006"+ + "\u00ea\u0000\u0000\u06e7\u01e5\u0001\u0000\u0000\u0000\u06e8\u06e9\u0003"+ + "\u0012\u0001\u0000\u06e9\u06ea\u0001\u0000\u0000\u0000\u06ea\u06eb\u0006"+ + "\u00eb\u0000\u0000\u06eb\u01e7\u0001\u0000\u0000\u0000\u06ec\u06ed\u0003"+ + "\u0014\u0002\u0000\u06ed\u06ee\u0001\u0000\u0000\u0000\u06ee\u06ef\u0006"+ + "\u00ec\u0000\u0000\u06ef\u01e9\u0001\u0000\u0000\u0000F\u0000\u0001\u0002"+ + "\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u01f0\u01f4"+ + "\u01f7\u0200\u0202\u020d\u0323\u0369\u036d\u0372\u03cc\u03ce\u0401\u0406"+ + "\u040f\u0416\u041b\u041d\u0428\u0430\u0433\u0435\u043a\u043f\u0445\u044c"+ + "\u0451\u0457\u045a\u0462\u0466\u04f1\u04f6\u04fd\u04ff\u0504\u0509\u0510"+ + "\u0512\u052c\u0531\u0536\u0538\u053e\u0579\u057e\u067d\u0681\u0686\u068b"+ + "\u0690\u0692\u0696\u0698)\u0000\u0001\u0000\u0005\u0001\u0000\u0005\u0002"+ + "\u0000\u0005\u0005\u0000\u0005\u0006\u0000\u0005\u0007\u0000\u0005\b\u0000"+ + "\u0005\t\u0000\u0005\n\u0000\u0005\f\u0000\u0005\r\u0000\u0005\u000e\u0000"+ + "\u0005\u000f\u0000\u00074\u0000\u0004\u0000\u0000\u0007J\u0000\u0007\u0084"+ + "\u0000\u0007@\u0000\u0007>\u0000\u0007f\u0000\u0007e\u0000\u0007a\u0000"+ + "\u0005\u0004\u0000\u0005\u0003\u0000\u0007O\u0000\u0007&\u0000\u0007:"+ + "\u0000\u0007\u0080\u0000\u0007L\u0000\u0007_\u0000\u0007^\u0000\u0007"+ + "`\u0000\u0007b\u0000\u0007=\u0000\u0005\u0000\u0000\u0007\u000f\u0000"+ + "\u0007<\u0000\u0007k\u0000\u00075\u0000\u0007c\u0000\u0005\u000b\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 30994e3f1ba88..7638709eb9577 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 @@ -56,7 +56,6 @@ null null null 'and' -'as' 'asc' '=' 'by' @@ -132,6 +131,7 @@ null null null null +'as' null null null @@ -198,7 +198,6 @@ QUOTED_STRING INTEGER_LITERAL DECIMAL_LITERAL AND -AS ASC ASSIGN BY @@ -274,6 +273,7 @@ ID_PATTERN PROJECT_LINE_COMMENT PROJECT_MULTILINE_COMMENT PROJECT_WS +AS RENAME_LINE_COMMENT RENAME_MULTILINE_COMMENT RENAME_WS @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 770, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 551, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 558, 8, 61, 10, 61, 12, 61, 561, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 568, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 573, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 581, 8, 61, 10, 61, 12, 61, 584, 9, 61, 1, 62, 1, 62, 3, 62, 588, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 595, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 600, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 605, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 615, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 621, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 629, 8, 65, 10, 65, 12, 65, 632, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 642, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 647, 8, 66, 10, 66, 12, 66, 650, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 658, 8, 67, 10, 67, 12, 67, 661, 9, 67, 1, 67, 1, 67, 3, 67, 665, 8, 67, 3, 67, 667, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 677, 8, 69, 10, 69, 12, 69, 680, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 701, 8, 71, 10, 71, 12, 71, 704, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 712, 8, 71, 10, 71, 12, 71, 715, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 723, 8, 71, 10, 71, 12, 71, 726, 9, 71, 1, 71, 1, 71, 3, 71, 730, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 736, 8, 73, 1, 74, 3, 74, 739, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 744, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 763, 8, 80, 10, 80, 12, 80, 766, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 801, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 572, 1, 0, 0, 0, 124, 599, 1, 0, 0, 0, 126, 601, 1, 0, 0, 0, 128, 614, 1, 0, 0, 0, 130, 620, 1, 0, 0, 0, 132, 641, 1, 0, 0, 0, 134, 651, 1, 0, 0, 0, 136, 670, 1, 0, 0, 0, 138, 672, 1, 0, 0, 0, 140, 683, 1, 0, 0, 0, 142, 729, 1, 0, 0, 0, 144, 731, 1, 0, 0, 0, 146, 735, 1, 0, 0, 0, 148, 738, 1, 0, 0, 0, 150, 743, 1, 0, 0, 0, 152, 747, 1, 0, 0, 0, 154, 749, 1, 0, 0, 0, 156, 751, 1, 0, 0, 0, 158, 756, 1, 0, 0, 0, 160, 758, 1, 0, 0, 0, 162, 767, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 542, 3, 148, 74, 0, 542, 121, 1, 0, 0, 0, 543, 544, 6, 61, -1, 0, 544, 545, 5, 72, 0, 0, 545, 573, 3, 122, 61, 8, 546, 573, 3, 128, 64, 0, 547, 573, 3, 124, 62, 0, 548, 550, 3, 128, 64, 0, 549, 551, 5, 72, 0, 0, 550, 549, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 5, 68, 0, 0, 553, 554, 5, 100, 0, 0, 554, 559, 3, 128, 64, 0, 555, 556, 5, 63, 0, 0, 556, 558, 3, 128, 64, 0, 557, 555, 1, 0, 0, 0, 558, 561, 1, 0, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 562, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 562, 563, 5, 101, 0, 0, 563, 573, 1, 0, 0, 0, 564, 565, 3, 128, 64, 0, 565, 567, 5, 69, 0, 0, 566, 568, 5, 72, 0, 0, 567, 566, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 5, 73, 0, 0, 570, 573, 1, 0, 0, 0, 571, 573, 3, 126, 63, 0, 572, 543, 1, 0, 0, 0, 572, 546, 1, 0, 0, 0, 572, 547, 1, 0, 0, 0, 572, 548, 1, 0, 0, 0, 572, 564, 1, 0, 0, 0, 572, 571, 1, 0, 0, 0, 573, 582, 1, 0, 0, 0, 574, 575, 10, 5, 0, 0, 575, 576, 5, 56, 0, 0, 576, 581, 3, 122, 61, 6, 577, 578, 10, 4, 0, 0, 578, 579, 5, 76, 0, 0, 579, 581, 3, 122, 61, 5, 580, 574, 1, 0, 0, 0, 580, 577, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 123, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 3, 128, 64, 0, 586, 588, 5, 72, 0, 0, 587, 586, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 5, 71, 0, 0, 590, 591, 3, 152, 76, 0, 591, 600, 1, 0, 0, 0, 592, 594, 3, 128, 64, 0, 593, 595, 5, 72, 0, 0, 594, 593, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 5, 78, 0, 0, 597, 598, 3, 152, 76, 0, 598, 600, 1, 0, 0, 0, 599, 585, 1, 0, 0, 0, 599, 592, 1, 0, 0, 0, 600, 125, 1, 0, 0, 0, 601, 604, 3, 46, 23, 0, 602, 603, 5, 61, 0, 0, 603, 605, 3, 10, 5, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 5, 62, 0, 0, 607, 608, 3, 142, 71, 0, 608, 127, 1, 0, 0, 0, 609, 615, 3, 130, 65, 0, 610, 611, 3, 130, 65, 0, 611, 612, 3, 154, 77, 0, 612, 613, 3, 130, 65, 0, 613, 615, 1, 0, 0, 0, 614, 609, 1, 0, 0, 0, 614, 610, 1, 0, 0, 0, 615, 129, 1, 0, 0, 0, 616, 617, 6, 65, -1, 0, 617, 621, 3, 132, 66, 0, 618, 619, 7, 4, 0, 0, 619, 621, 3, 130, 65, 3, 620, 616, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 630, 1, 0, 0, 0, 622, 623, 10, 2, 0, 0, 623, 624, 7, 5, 0, 0, 624, 629, 3, 130, 65, 3, 625, 626, 10, 1, 0, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 2, 628, 622, 1, 0, 0, 0, 628, 625, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 131, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 6, 66, -1, 0, 634, 642, 3, 142, 71, 0, 635, 642, 3, 46, 23, 0, 636, 642, 3, 134, 67, 0, 637, 638, 5, 100, 0, 0, 638, 639, 3, 122, 61, 0, 639, 640, 5, 101, 0, 0, 640, 642, 1, 0, 0, 0, 641, 633, 1, 0, 0, 0, 641, 635, 1, 0, 0, 0, 641, 636, 1, 0, 0, 0, 641, 637, 1, 0, 0, 0, 642, 648, 1, 0, 0, 0, 643, 644, 10, 1, 0, 0, 644, 645, 5, 61, 0, 0, 645, 647, 3, 10, 5, 0, 646, 643, 1, 0, 0, 0, 647, 650, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 133, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 651, 652, 3, 136, 68, 0, 652, 666, 5, 100, 0, 0, 653, 667, 5, 90, 0, 0, 654, 659, 3, 122, 61, 0, 655, 656, 5, 63, 0, 0, 656, 658, 3, 122, 61, 0, 657, 655, 1, 0, 0, 0, 658, 661, 1, 0, 0, 0, 659, 657, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 664, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 662, 663, 5, 63, 0, 0, 663, 665, 3, 138, 69, 0, 664, 662, 1, 0, 0, 0, 664, 665, 1, 0, 0, 0, 665, 667, 1, 0, 0, 0, 666, 653, 1, 0, 0, 0, 666, 654, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 669, 5, 101, 0, 0, 669, 135, 1, 0, 0, 0, 670, 671, 3, 60, 30, 0, 671, 137, 1, 0, 0, 0, 672, 673, 5, 93, 0, 0, 673, 678, 3, 140, 70, 0, 674, 675, 5, 63, 0, 0, 675, 677, 3, 140, 70, 0, 676, 674, 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, 682, 5, 94, 0, 0, 682, 139, 1, 0, 0, 0, 683, 684, 3, 152, 76, 0, 684, 685, 5, 62, 0, 0, 685, 686, 3, 142, 71, 0, 686, 141, 1, 0, 0, 0, 687, 730, 5, 73, 0, 0, 688, 689, 3, 150, 75, 0, 689, 690, 5, 102, 0, 0, 690, 730, 1, 0, 0, 0, 691, 730, 3, 148, 74, 0, 692, 730, 3, 150, 75, 0, 693, 730, 3, 144, 72, 0, 694, 730, 3, 56, 28, 0, 695, 730, 3, 152, 76, 0, 696, 697, 5, 98, 0, 0, 697, 702, 3, 146, 73, 0, 698, 699, 5, 63, 0, 0, 699, 701, 3, 146, 73, 0, 700, 698, 1, 0, 0, 0, 701, 704, 1, 0, 0, 0, 702, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 705, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 730, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 713, 3, 144, 72, 0, 709, 710, 5, 63, 0, 0, 710, 712, 3, 144, 72, 0, 711, 709, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 99, 0, 0, 717, 730, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 724, 3, 152, 76, 0, 720, 721, 5, 63, 0, 0, 721, 723, 3, 152, 76, 0, 722, 720, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 727, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 728, 5, 99, 0, 0, 728, 730, 1, 0, 0, 0, 729, 687, 1, 0, 0, 0, 729, 688, 1, 0, 0, 0, 729, 691, 1, 0, 0, 0, 729, 692, 1, 0, 0, 0, 729, 693, 1, 0, 0, 0, 729, 694, 1, 0, 0, 0, 729, 695, 1, 0, 0, 0, 729, 696, 1, 0, 0, 0, 729, 707, 1, 0, 0, 0, 729, 718, 1, 0, 0, 0, 730, 143, 1, 0, 0, 0, 731, 732, 7, 6, 0, 0, 732, 145, 1, 0, 0, 0, 733, 736, 3, 148, 74, 0, 734, 736, 3, 150, 75, 0, 735, 733, 1, 0, 0, 0, 735, 734, 1, 0, 0, 0, 736, 147, 1, 0, 0, 0, 737, 739, 7, 4, 0, 0, 738, 737, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 5, 55, 0, 0, 741, 149, 1, 0, 0, 0, 742, 744, 7, 4, 0, 0, 743, 742, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 5, 54, 0, 0, 746, 151, 1, 0, 0, 0, 747, 748, 5, 53, 0, 0, 748, 153, 1, 0, 0, 0, 749, 750, 7, 7, 0, 0, 750, 155, 1, 0, 0, 0, 751, 752, 7, 8, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 3, 158, 79, 0, 754, 755, 3, 160, 80, 0, 755, 157, 1, 0, 0, 0, 756, 757, 3, 28, 14, 0, 757, 159, 1, 0, 0, 0, 758, 759, 5, 75, 0, 0, 759, 764, 3, 162, 81, 0, 760, 761, 5, 63, 0, 0, 761, 763, 3, 162, 81, 0, 762, 760, 1, 0, 0, 0, 763, 766, 1, 0, 0, 0, 764, 762, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 161, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 768, 3, 128, 64, 0, 768, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 550, 559, 567, 572, 580, 582, 587, 594, 599, 604, 614, 620, 628, 630, 641, 648, 659, 664, 666, 678, 702, 713, 724, 729, 735, 738, 743, 764] \ No newline at end of file +[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 537, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 803, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 542, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 62, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 58, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 98, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 74, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 79, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 62, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 58, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 74, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 59, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 74, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 132, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 62, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 99, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 100, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 74, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 79, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 536, 5, 7, 0, 0, 533, 534, 3, 46, 23, 0, 534, 535, 5, 58, 0, 0, 535, 537, 1, 0, 0, 0, 536, 533, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 3, 132, 66, 0, 539, 540, 5, 79, 0, 0, 540, 541, 3, 60, 30, 0, 541, 119, 1, 0, 0, 0, 542, 543, 5, 18, 0, 0, 543, 544, 3, 148, 74, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 71, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 71, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 67, 0, 0, 555, 556, 5, 99, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 62, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 100, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 68, 0, 0, 568, 570, 5, 71, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 72, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 75, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 71, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 70, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 71, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 77, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 60, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 61, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 99, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 100, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 60, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 99, 0, 0, 655, 669, 5, 89, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 62, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 62, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 100, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 92, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 62, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 93, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 61, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 72, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 101, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 97, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 62, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 97, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 62, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 97, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 62, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 98, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 114, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 74, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 62, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 536, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ 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 9d09b0dd50da0..6f3c676e44abc 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 @@ -36,24 +36,23 @@ public class EsqlBaseParser extends ParserConfig { ENRICH_FIELD_WS=44, SETTING=45, SETTING_LINE_COMMENT=46, SETTTING_MULTILINE_COMMENT=47, SETTING_WS=48, EXPLAIN_WS=49, EXPLAIN_LINE_COMMENT=50, EXPLAIN_MULTILINE_COMMENT=51, PIPE=52, QUOTED_STRING=53, INTEGER_LITERAL=54, DECIMAL_LITERAL=55, AND=56, - AS=57, ASC=58, ASSIGN=59, BY=60, CAST_OP=61, COLON=62, COMMA=63, DESC=64, - DOT=65, FALSE=66, FIRST=67, IN=68, IS=69, LAST=70, LIKE=71, NOT=72, NULL=73, - NULLS=74, ON=75, OR=76, PARAM=77, RLIKE=78, TRUE=79, WITH=80, EQ=81, CIEQ=82, - NEQ=83, LT=84, LTE=85, GT=86, GTE=87, PLUS=88, MINUS=89, ASTERISK=90, - SLASH=91, PERCENT=92, LEFT_BRACES=93, RIGHT_BRACES=94, DOUBLE_PARAMS=95, - NAMED_OR_POSITIONAL_PARAM=96, NAMED_OR_POSITIONAL_DOUBLE_PARAMS=97, OPENING_BRACKET=98, - CLOSING_BRACKET=99, LP=100, RP=101, UNQUOTED_IDENTIFIER=102, QUOTED_IDENTIFIER=103, - EXPR_LINE_COMMENT=104, EXPR_MULTILINE_COMMENT=105, EXPR_WS=106, METADATA=107, - UNQUOTED_SOURCE=108, FROM_LINE_COMMENT=109, FROM_MULTILINE_COMMENT=110, - FROM_WS=111, FORK_WS=112, FORK_LINE_COMMENT=113, FORK_MULTILINE_COMMENT=114, - JOIN=115, USING=116, JOIN_LINE_COMMENT=117, JOIN_MULTILINE_COMMENT=118, - JOIN_WS=119, LOOKUP_LINE_COMMENT=120, LOOKUP_MULTILINE_COMMENT=121, LOOKUP_WS=122, - LOOKUP_FIELD_LINE_COMMENT=123, LOOKUP_FIELD_MULTILINE_COMMENT=124, LOOKUP_FIELD_WS=125, - MVEXPAND_LINE_COMMENT=126, MVEXPAND_MULTILINE_COMMENT=127, MVEXPAND_WS=128, - ID_PATTERN=129, PROJECT_LINE_COMMENT=130, PROJECT_MULTILINE_COMMENT=131, - PROJECT_WS=132, RENAME_LINE_COMMENT=133, RENAME_MULTILINE_COMMENT=134, - RENAME_WS=135, INFO=136, SHOW_LINE_COMMENT=137, SHOW_MULTILINE_COMMENT=138, - SHOW_WS=139; + ASC=57, ASSIGN=58, BY=59, CAST_OP=60, COLON=61, COMMA=62, DESC=63, DOT=64, + FALSE=65, FIRST=66, IN=67, IS=68, LAST=69, LIKE=70, NOT=71, NULL=72, NULLS=73, + ON=74, OR=75, PARAM=76, RLIKE=77, TRUE=78, WITH=79, EQ=80, CIEQ=81, NEQ=82, + LT=83, LTE=84, GT=85, GTE=86, PLUS=87, MINUS=88, ASTERISK=89, SLASH=90, + PERCENT=91, LEFT_BRACES=92, RIGHT_BRACES=93, DOUBLE_PARAMS=94, NAMED_OR_POSITIONAL_PARAM=95, + NAMED_OR_POSITIONAL_DOUBLE_PARAMS=96, OPENING_BRACKET=97, CLOSING_BRACKET=98, + LP=99, RP=100, UNQUOTED_IDENTIFIER=101, QUOTED_IDENTIFIER=102, EXPR_LINE_COMMENT=103, + EXPR_MULTILINE_COMMENT=104, EXPR_WS=105, METADATA=106, UNQUOTED_SOURCE=107, + FROM_LINE_COMMENT=108, FROM_MULTILINE_COMMENT=109, FROM_WS=110, FORK_WS=111, + FORK_LINE_COMMENT=112, FORK_MULTILINE_COMMENT=113, JOIN=114, USING=115, + JOIN_LINE_COMMENT=116, JOIN_MULTILINE_COMMENT=117, JOIN_WS=118, LOOKUP_LINE_COMMENT=119, + LOOKUP_MULTILINE_COMMENT=120, LOOKUP_WS=121, LOOKUP_FIELD_LINE_COMMENT=122, + LOOKUP_FIELD_MULTILINE_COMMENT=123, LOOKUP_FIELD_WS=124, MVEXPAND_LINE_COMMENT=125, + MVEXPAND_MULTILINE_COMMENT=126, MVEXPAND_WS=127, ID_PATTERN=128, PROJECT_LINE_COMMENT=129, + PROJECT_MULTILINE_COMMENT=130, PROJECT_WS=131, AS=132, RENAME_LINE_COMMENT=133, + RENAME_MULTILINE_COMMENT=134, RENAME_WS=135, INFO=136, SHOW_LINE_COMMENT=137, + SHOW_MULTILINE_COMMENT=138, SHOW_WS=139; public static final int RULE_singleStatement = 0, RULE_query = 1, RULE_sourceCommand = 2, RULE_processingCommand = 3, RULE_whereCommand = 4, RULE_dataType = 5, RULE_rowCommand = 6, RULE_fields = 7, @@ -115,14 +114,14 @@ private static String[] makeLiteralNames() { null, null, null, "'mv_expand'", "'drop'", "'keep'", null, null, "'rename'", "'show'", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "'|'", null, null, null, - "'and'", "'as'", "'asc'", "'='", "'by'", "'::'", "':'", "','", "'desc'", - "'.'", "'false'", "'first'", "'in'", "'is'", "'last'", "'like'", "'not'", - "'null'", "'nulls'", "'on'", "'or'", "'?'", "'rlike'", "'true'", "'with'", - "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", - "'/'", "'%'", "'{'", "'}'", "'??'", null, null, null, "']'", null, "')'", - null, null, null, null, null, "'metadata'", null, null, null, null, null, - null, null, "'join'", "'USING'", null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, + "'and'", "'asc'", "'='", "'by'", "'::'", "':'", "','", "'desc'", "'.'", + "'false'", "'first'", "'in'", "'is'", "'last'", "'like'", "'not'", "'null'", + "'nulls'", "'on'", "'or'", "'?'", "'rlike'", "'true'", "'with'", "'=='", + "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", + "'%'", "'{'", "'}'", "'??'", null, null, null, "']'", null, "')'", null, + null, null, null, null, "'metadata'", null, null, null, null, null, null, + null, "'join'", "'USING'", null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, "'as'", null, null, null, "'info'" }; } @@ -140,7 +139,7 @@ private static String[] makeSymbolicNames() { "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "EXPLAIN_WS", "EXPLAIN_LINE_COMMENT", "EXPLAIN_MULTILINE_COMMENT", "PIPE", "QUOTED_STRING", "INTEGER_LITERAL", - "DECIMAL_LITERAL", "AND", "AS", "ASC", "ASSIGN", "BY", "CAST_OP", "COLON", + "DECIMAL_LITERAL", "AND", "ASC", "ASSIGN", "BY", "CAST_OP", "COLON", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "IN", "IS", "LAST", "LIKE", "NOT", "NULL", "NULLS", "ON", "OR", "PARAM", "RLIKE", "TRUE", "WITH", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", @@ -154,8 +153,8 @@ private static String[] makeSymbolicNames() { "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", - "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", "INFO", - "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" + "AS", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", + "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS" }; } private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames(); @@ -4397,9 +4396,9 @@ public final RerankCommandContext rerankCommand() throws RecognitionException { @SuppressWarnings("CheckReturnValue") public static class CompletionCommandContext extends ParserRuleContext { + public QualifiedNameContext targetField; public PrimaryExpressionContext prompt; public IdentifierOrParameterContext inferenceId; - public QualifiedNameContext targetField; public TerminalNode COMPLETION() { return getToken(EsqlBaseParser.COMPLETION, 0); } public TerminalNode WITH() { return getToken(EsqlBaseParser.WITH, 0); } public PrimaryExpressionContext primaryExpression() { @@ -4408,7 +4407,7 @@ public PrimaryExpressionContext primaryExpression() { public IdentifierOrParameterContext identifierOrParameter() { return getRuleContext(IdentifierOrParameterContext.class,0); } - public TerminalNode AS() { return getToken(EsqlBaseParser.AS, 0); } + public TerminalNode ASSIGN() { return getToken(EsqlBaseParser.ASSIGN, 0); } public QualifiedNameContext qualifiedName() { return getRuleContext(QualifiedNameContext.class,0); } @@ -4440,24 +4439,24 @@ public final CompletionCommandContext completionCommand() throws RecognitionExce { setState(532); match(COMPLETION); - setState(533); - ((CompletionCommandContext)_localctx).prompt = primaryExpression(0); - setState(534); - match(WITH); - setState(535); - ((CompletionCommandContext)_localctx).inferenceId = identifierOrParameter(); - setState(538); + setState(536); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,41,_ctx) ) { case 1: { - setState(536); - match(AS); - setState(537); + setState(533); ((CompletionCommandContext)_localctx).targetField = qualifiedName(); + setState(534); + match(ASSIGN); } break; } + setState(538); + ((CompletionCommandContext)_localctx).prompt = primaryExpression(0); + setState(539); + match(WITH); + setState(540); + ((CompletionCommandContext)_localctx).inferenceId = identifierOrParameter(); } } catch (RecognitionException re) { @@ -4504,9 +4503,9 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(540); + setState(542); match(DEV_SAMPLE); - setState(541); + setState(543); ((SampleCommandContext)_localctx).probability = decimalValue(); } } @@ -4722,7 +4721,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(572); + setState(574); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { case 1: @@ -4731,9 +4730,9 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(544); + setState(546); match(NOT); - setState(545); + setState(547); booleanExpression(8); } break; @@ -4742,7 +4741,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(548); valueExpression(); } break; @@ -4751,7 +4750,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(547); + setState(549); regexBooleanExpression(); } break; @@ -4760,41 +4759,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); - valueExpression(); setState(550); + valueExpression(); + setState(552); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(549); + setState(551); match(NOT); } } - setState(552); + setState(554); match(IN); - setState(553); + setState(555); match(LP); - setState(554); + setState(556); valueExpression(); - setState(559); + setState(561); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(555); + setState(557); match(COMMA); - setState(556); + setState(558); valueExpression(); } } - setState(561); + setState(563); _errHandler.sync(this); _la = _input.LA(1); } - setState(562); + setState(564); match(RP); } break; @@ -4803,21 +4802,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(564); + setState(566); valueExpression(); - setState(565); - match(IS); setState(567); + match(IS); + setState(569); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(566); + setState(568); match(NOT); } } - setState(569); + setState(571); match(NULL); } break; @@ -4826,13 +4825,13 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(571); + setState(573); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(582); + setState(584); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -4840,7 +4839,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(580); + setState(582); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: @@ -4848,11 +4847,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(574); + setState(576); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(575); + setState(577); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(576); + setState(578); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4861,18 +4860,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(577); + setState(579); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(578); + setState(580); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(579); + setState(581); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(584); + setState(586); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); } @@ -4927,48 +4926,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(599); + setState(601); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(585); - valueExpression(); setState(587); + valueExpression(); + setState(589); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(586); + setState(588); match(NOT); } } - setState(589); + setState(591); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(590); + setState(592); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(592); - valueExpression(); setState(594); + valueExpression(); + setState(596); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(593); + setState(595); match(NOT); } } - setState(596); + setState(598); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(597); + setState(599); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5028,23 +5027,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(601); + setState(603); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(604); + setState(606); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(602); + setState(604); match(CAST_OP); - setState(603); + setState(605); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(606); + setState(608); match(COLON); - setState(607); + setState(609); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5128,14 +5127,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(614); + setState(616); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(609); + setState(611); operatorExpression(0); } break; @@ -5143,11 +5142,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(610); + setState(612); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(611); + setState(613); comparisonOperator(); - setState(612); + setState(614); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5272,7 +5271,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(620); + setState(622); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { case 1: @@ -5281,7 +5280,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _ctx = _localctx; _prevctx = _localctx; - setState(617); + setState(619); primaryExpression(0); } break; @@ -5290,7 +5289,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(618); + setState(620); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5301,13 +5300,13 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(619); + setState(621); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(630); + setState(632); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -5315,7 +5314,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(628); + setState(630); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { case 1: @@ -5323,12 +5322,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(622); + setState(624); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(623); + setState(625); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); - if ( !(((((_la - 90)) & ~0x3f) == 0 && ((1L << (_la - 90)) & 7L) != 0)) ) { + if ( !(((((_la - 89)) & ~0x3f) == 0 && ((1L << (_la - 89)) & 7L) != 0)) ) { ((ArithmeticBinaryContext)_localctx).operator = (Token)_errHandler.recoverInline(this); } else { @@ -5336,7 +5335,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(624); + setState(626); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5345,9 +5344,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(625); + setState(627); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(626); + setState(628); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5358,14 +5357,14 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(627); + setState(629); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(632); + setState(634); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); } @@ -5523,7 +5522,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(641); + setState(643); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { case 1: @@ -5532,7 +5531,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(634); + setState(636); constant(); } break; @@ -5541,7 +5540,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(635); + setState(637); qualifiedName(); } break; @@ -5550,7 +5549,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(638); functionExpression(); } break; @@ -5559,17 +5558,17 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(639); match(LP); - setState(638); + setState(640); booleanExpression(0); - setState(639); + setState(641); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(648); + setState(650); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -5580,16 +5579,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(643); + setState(645); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(644); + setState(646); match(CAST_OP); - setState(645); + setState(647); dataType(); } } } - setState(650); + setState(652); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); } @@ -5655,16 +5654,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(651); + setState(653); functionName(); - setState(652); + setState(654); match(LP); - setState(666); + setState(668); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(653); + setState(655); match(ASTERISK); } break; @@ -5687,34 +5686,34 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(654); + setState(656); booleanExpression(0); - setState(659); + setState(661); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(655); + setState(657); match(COMMA); - setState(656); + setState(658); booleanExpression(0); } } } - setState(661); + setState(663); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } - setState(664); + setState(666); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(662); + setState(664); match(COMMA); - setState(663); + setState(665); mapExpression(); } } @@ -5727,7 +5726,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(668); + setState(670); match(RP); } } @@ -5773,7 +5772,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(670); + setState(672); identifierOrParameter(); } } @@ -5829,27 +5828,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(674); match(LEFT_BRACES); - setState(673); + setState(675); entryExpression(); - setState(678); + setState(680); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(674); + setState(676); match(COMMA); - setState(675); + setState(677); entryExpression(); } } - setState(680); + setState(682); _errHandler.sync(this); _la = _input.LA(1); } - setState(681); + setState(683); match(RIGHT_BRACES); } } @@ -5901,11 +5900,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(683); + setState(685); ((EntryExpressionContext)_localctx).key = string(); - setState(684); + setState(686); match(COLON); - setState(685); + setState(687); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6176,14 +6175,14 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(729); + setState(731); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(687); + setState(689); match(NULL); } break; @@ -6191,9 +6190,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(688); + setState(690); integerValue(); - setState(689); + setState(691); match(UNQUOTED_IDENTIFIER); } break; @@ -6201,7 +6200,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(691); + setState(693); decimalValue(); } break; @@ -6209,7 +6208,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(692); + setState(694); integerValue(); } break; @@ -6217,7 +6216,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(693); + setState(695); booleanValue(); } break; @@ -6225,7 +6224,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(694); + setState(696); parameter(); } break; @@ -6233,7 +6232,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(695); + setState(697); string(); } break; @@ -6241,27 +6240,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(696); + setState(698); match(OPENING_BRACKET); - setState(697); + setState(699); numericValue(); - setState(702); + setState(704); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(698); + setState(700); match(COMMA); - setState(699); + setState(701); numericValue(); } } - setState(704); + setState(706); _errHandler.sync(this); _la = _input.LA(1); } - setState(705); + setState(707); match(CLOSING_BRACKET); } break; @@ -6269,27 +6268,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(707); + setState(709); match(OPENING_BRACKET); - setState(708); + setState(710); booleanValue(); - setState(713); + setState(715); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(709); + setState(711); match(COMMA); - setState(710); + setState(712); booleanValue(); } } - setState(715); + setState(717); _errHandler.sync(this); _la = _input.LA(1); } - setState(716); + setState(718); match(CLOSING_BRACKET); } break; @@ -6297,27 +6296,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(718); + setState(720); match(OPENING_BRACKET); - setState(719); + setState(721); string(); - setState(724); + setState(726); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(720); + setState(722); match(COMMA); - setState(721); + setState(723); string(); } } - setState(726); + setState(728); _errHandler.sync(this); _la = _input.LA(1); } - setState(727); + setState(729); match(CLOSING_BRACKET); } break; @@ -6365,7 +6364,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(731); + setState(733); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6420,20 +6419,20 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(735); + setState(737); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(733); + setState(735); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(734); + setState(736); integerValue(); } break; @@ -6482,12 +6481,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(738); + setState(740); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(737); + setState(739); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6500,7 +6499,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(740); + setState(742); match(DECIMAL_LITERAL); } } @@ -6547,12 +6546,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(743); + setState(745); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(742); + setState(744); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6565,7 +6564,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(745); + setState(747); match(INTEGER_LITERAL); } } @@ -6609,7 +6608,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(747); + setState(749); match(QUOTED_STRING); } } @@ -6659,9 +6658,9 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(751); _la = _input.LA(1); - if ( !(((((_la - 81)) & ~0x3f) == 0 && ((1L << (_la - 81)) & 125L) != 0)) ) { + if ( !(((((_la - 80)) & ~0x3f) == 0 && ((1L << (_la - 80)) & 125L) != 0)) ) { _errHandler.recoverInline(this); } else { @@ -6722,7 +6721,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(753); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6733,11 +6732,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(752); + setState(754); match(JOIN); - setState(753); + setState(755); joinTarget(); - setState(754); + setState(756); joinCondition(); } } @@ -6784,7 +6783,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(756); + setState(758); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6839,25 +6838,25 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(758); + setState(760); match(ON); - setState(759); + setState(761); joinPredicate(); - setState(764); + setState(766); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(760); + setState(762); match(COMMA); - setState(761); + setState(763); joinPredicate(); } } } - setState(766); + setState(768); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); } @@ -6905,7 +6904,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(767); + setState(769); valueExpression(); } } @@ -7006,7 +7005,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0302\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u0304\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"+ @@ -7074,250 +7073,250 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "5\u01f0\b5\u000b5\f5\u01f1\u00016\u00016\u00016\u00016\u00017\u00017\u0001"+ "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ - ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0001"+ - ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0003=\u0227\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ - "=\u0005=\u022e\b=\n=\f=\u0231\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003"+ - "=\u0238\b=\u0001=\u0001=\u0001=\u0003=\u023d\b=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0245\b=\n=\f=\u0248\t=\u0001>\u0001>\u0003>\u024c"+ - "\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0253\b>\u0001>\u0001>\u0001"+ - ">\u0003>\u0258\b>\u0001?\u0001?\u0001?\u0003?\u025d\b?\u0001?\u0001?\u0001"+ - "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0267\b@\u0001A\u0001A\u0001"+ - "A\u0001A\u0003A\u026d\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ - "A\u0275\bA\nA\fA\u0278\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0003B\u0282\bB\u0001B\u0001B\u0001B\u0005B\u0287\bB\nB\fB\u028a"+ - "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0292\bC\nC\fC\u0295"+ - "\tC\u0001C\u0001C\u0003C\u0299\bC\u0003C\u029b\bC\u0001C\u0001C\u0001"+ - "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a5\bE\nE\fE\u02a8\tE\u0001"+ - "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02bd"+ - "\bG\nG\fG\u02c0\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c8"+ - "\bG\nG\fG\u02cb\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d3"+ - "\bG\nG\fG\u02d6\tG\u0001G\u0001G\u0003G\u02da\bG\u0001H\u0001H\u0001I"+ - "\u0001I\u0003I\u02e0\bI\u0001J\u0003J\u02e3\bJ\u0001J\u0001J\u0001K\u0003"+ - "K\u02e8\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ - "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u02fb"+ - "\bP\nP\fP\u02fe\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ - "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ - "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ - "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ - "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055ll\u0001\u0000fg\u0002\u0000"+ - "::@@\u0002\u0000CCFF\u0001\u0000XY\u0001\u0000Z\\\u0002\u0000BBOO\u0002"+ - "\u0000QQSW\u0002\u0000\u0016\u0016\u0018\u0019\u0321\u0000\u00a4\u0001"+ - "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ - "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ - "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ - "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ - "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ - "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ - "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ - "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ - "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ - "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ - "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ - "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ - "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ - "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ - ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ - "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ - "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0191\u0001\u0000\u0000\u0000"+ - "L\u0195\u0001\u0000\u0000\u0000N\u019b\u0001\u0000\u0000\u0000P\u019f"+ - "\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000\u0000\u0000T\u01aa\u0001\u0000"+ - "\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000X\u01b1\u0001\u0000\u0000\u0000"+ - "Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8\u0001\u0000\u0000\u0000^\u01cc"+ - "\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000\u0000\u0000b\u01d5\u0001\u0000"+ - "\u0000\u0000d\u01db\u0001\u0000\u0000\u0000f\u01e8\u0001\u0000\u0000\u0000"+ - "h\u01eb\u0001\u0000\u0000\u0000j\u01ef\u0001\u0000\u0000\u0000l\u01f3"+ - "\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000\u0000\u0000p\u0208\u0001\u0000"+ - "\u0000\u0000r\u020a\u0001\u0000\u0000\u0000t\u020c\u0001\u0000\u0000\u0000"+ - "v\u0214\u0001\u0000\u0000\u0000x\u021c\u0001\u0000\u0000\u0000z\u023c"+ - "\u0001\u0000\u0000\u0000|\u0257\u0001\u0000\u0000\u0000~\u0259\u0001\u0000"+ - "\u0000\u0000\u0080\u0266\u0001\u0000\u0000\u0000\u0082\u026c\u0001\u0000"+ - "\u0000\u0000\u0084\u0281\u0001\u0000\u0000\u0000\u0086\u028b\u0001\u0000"+ - "\u0000\u0000\u0088\u029e\u0001\u0000\u0000\u0000\u008a\u02a0\u0001\u0000"+ - "\u0000\u0000\u008c\u02ab\u0001\u0000\u0000\u0000\u008e\u02d9\u0001\u0000"+ - "\u0000\u0000\u0090\u02db\u0001\u0000\u0000\u0000\u0092\u02df\u0001\u0000"+ - "\u0000\u0000\u0094\u02e2\u0001\u0000\u0000\u0000\u0096\u02e7\u0001\u0000"+ - "\u0000\u0000\u0098\u02eb\u0001\u0000\u0000\u0000\u009a\u02ed\u0001\u0000"+ - "\u0000\u0000\u009c\u02ef\u0001\u0000\u0000\u0000\u009e\u02f4\u0001\u0000"+ - "\u0000\u0000\u00a0\u02f6\u0001\u0000\u0000\u0000\u00a2\u02ff\u0001\u0000"+ - "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ - "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ - "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ - "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ - "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ - "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ - "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ - "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ - "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ - "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ - "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ - "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ - "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ - "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ - "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ - "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ - "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ - "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ - "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ - "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ - "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ - "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ - "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ - "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ - "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ - "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ - "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ - "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ - "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ - "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ - "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ - "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005?\u0000"+ - "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ - "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ - "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ - "\u00eb\u0005;\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ - "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ - "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ - "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005?\u0000"+ - "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ - "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ - "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ - "\u00fa\u0005;\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ - "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ - "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ - "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ - "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ - "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005?\u0000\u0000"+ - "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ - "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ - "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ - "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ - "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ - "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ - "\u0110\u0005>\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ - "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ - "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ - "\"\u0011\u0000\u0115\u0116\u0005=\u0000\u0000\u0116\u0118\u0003 \u0010"+ - "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ - "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ - "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ - "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ - "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ - "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ - "\u0122\u0005k\u0000\u0000\u0122\u0127\u0005l\u0000\u0000\u0123\u0124\u0005"+ - "?\u0000\u0000\u0124\u0126\u0005l\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ - "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ - "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ - "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ - "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ - "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ - "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ - "\u0000\u0000\u0000\u0131\u0132\u0005<\u0000\u0000\u0132\u0134\u0003\u000e"+ - "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ - "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ - "\u0136\u0137\u0005?\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ - "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ - "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ - "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ - "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ - "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ - "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ - "\u0144\u0005A\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ - "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ - "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ - "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ - "\u0000\u014b\u014c\u0005A\u0000\u0000\u014c\u014e\u00036\u001b\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"+ - "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ - "\u00030\u0018\u0000\u0153\u0154\u0005?\u0000\u0000\u0154\u0156\u00030"+ - "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ - "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ - "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ - "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ - "\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ - "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ - "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ - "\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000\u0162\u0164\u0005`\u0000"+ - "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ - "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005_\u0000\u0000\u0166"+ - "\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ - "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ - "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ - "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ - "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ - "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ - "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ - "\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174\u0176\u0003B!\u0000"+ - "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ - "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ - "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ - "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ - "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ - "\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f\u0181\u0007\u0003"+ - "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ - "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ - "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ - "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ - "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ - "\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ - "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ - "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ - "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ - "\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194\u00030"+ - "\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b\u0000"+ - "\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000\u0198"+ - "\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a\u0001"+ - "\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005\n"+ - "\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098L\u0000"+ - "\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000\u01a0"+ - "\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7\u0003"+ - "T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ - "S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa\u01ab"+ - "\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003\u008e"+ - "G\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000\u0000"+ - "\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1\u01b2"+ - "\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4\u0005"+ - "c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!\u0000"+ - "\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000\u0000"+ - "\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000\u01ba"+ - "\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba\u0001"+ - "\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7\u0001"+ - "\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003^/"+ - "\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ - "\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000\u01c4"+ - "\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+ - "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7"+ - "\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8"+ - "]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb\u0005"+ - ";\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001\u0000"+ - "\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001\u0000"+ - "\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000\u0000"+ - "\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e\u0000"+ - "\u01d2\u01d3\u0005K\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4a"+ - "\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ - "\u0003*\u0015\u0000\u01d7\u01d8\u0005<\u0000\u0000\u01d8\u01da\u0003\u000e"+ + ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0003"+ + ";\u0219\b;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001<\u0001<\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ + ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ + ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ + "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ + "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ + "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ + "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ + "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ + "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ + "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ + "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ + "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ + "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ + "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ + "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ + "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ + "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ + "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ + "5kk\u0001\u0000ef\u0002\u000099??\u0002\u0000BBEE\u0001\u0000WX\u0001"+ + "\u0000Y[\u0002\u0000AANN\u0002\u0000PPRV\u0002\u0000\u0016\u0016\u0018"+ + "\u0019\u0323\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ + "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ + "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ + "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ + "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ + "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ + "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ + "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ + " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ + "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ + "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ + ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ + "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ + "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ + "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ + "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ + "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ + "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ + "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ + "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ + "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ + "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ + "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ + "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ + "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ + "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ + "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021e"+ + "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ + "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ + "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ + "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ + "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ + "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ + "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ + "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ + "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ + "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ + "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ + "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ + "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ + "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ + "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ + "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ + "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ + "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ + "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ + "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ + "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ + "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ + "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ + "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ + " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ + "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ + "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ + "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ + "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ + "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ + "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ + "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ + "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ + "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ + "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ + "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ + "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ + "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ + "\u0000\u00e2\u00e3\u0005>\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ + "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ + "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ + "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005:\u0000\u0000\u00eb\u00ed"+ + "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ + "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ + "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ + "\n\u0000\u00f1\u00f2\u0005>\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ + "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ + "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ + "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005:\u0000\u0000\u00fa\u00fc"+ + "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ + "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ + "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ + "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ + "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ + "\u0104\u0105\u0005>\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ + "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ + "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ + "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ + "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ + "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ + "\u0003\u001e\u000f\u0000\u010f\u0110\u0005=\u0000\u0000\u0110\u0112\u0001"+ + "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ + "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ + "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005<\u0000"+ + "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ + "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ + "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ + "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ + "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ + "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ + "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005j\u0000\u0000\u0122\u0127\u0005"+ + "k\u0000\u0000\u0123\u0124\u0005>\u0000\u0000\u0124\u0126\u0005k\u0000"+ + "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ + "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ + "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ + "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ + "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ + "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ + "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ + ";\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ + "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ + "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005>\u0000\u0000\u0137"+ + "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ + "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ + "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ + "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ + "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ + "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ + "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005@\u0000\u0000\u0144\u0146\u0003"+ + "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ + "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ + "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ + "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005@\u0000\u0000\u014c"+ + "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ + "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005>\u0000"+ + "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ + "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ + "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ + "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ + "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160"+ + "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ + "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ + "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000"+ + "\u0162\u0164\u0005_\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ + "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ + "\u0005^\u0000\u0000\u0166\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000"+ + "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ + "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ + "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ + "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ + "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ + "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ + "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174"+ + "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ + "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ + "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ + "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ + "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ + "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f"+ + "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ + "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ + "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ + "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ + "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ + "\u018e\u0003J%\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003"+ + "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ + "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ + "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ + "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193"+ + "\u0194\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005"+ + "\b\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098"+ + "L\u0000\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000"+ + "\u0199\u019a\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b"+ + "\u019c\u0005\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e"+ + "\u0003\u0098L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b"+ + "\u0000\u0000\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000"+ + "\u01a2\u01a7\u0003T*\u0000\u01a3\u01a4\u0005>\u0000\u0000\u01a4\u01a6"+ + "\u0003T*\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\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000"+ + "\u0000\u01aa\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005:\u0000\u0000\u01ac"+ + "\u01ad\u0003\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005"+ + "\u0006\u0000\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000"+ + "\u0000\u01b1\u01b2\u0005a\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000"+ + "\u01b3\u01b4\u0005b\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6"+ + "\u0005!\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000"+ + "\u0000\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000"+ + "\u0000\u01ba\u01bb\u0005J\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc"+ + "\u01ba\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd"+ + "\u01c7\u0001\u0000\u0000\u0000\u01be\u01bf\u0005O\u0000\u0000\u01bf\u01c4"+ + "\u0003^/\u0000\u01c0\u01c1\u0005>\u0000\u0000\u01c1\u01c3\u0003^/\u0000"+ + "\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000"+ + "\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000"+ + "\u01c5\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000"+ + "\u01c7\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000"+ + "\u01c8]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb"+ + "\u0005:\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001"+ + "\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001"+ + "\u0000\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000"+ + "\u0000\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e"+ + "\u0000\u01d2\u01d3\u0005J\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4"+ + "a\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ + "\u0003*\u0015\u0000\u01d7\u01d8\u0005;\u0000\u0000\u01d8\u01da\u0003\u000e"+ "\u0007\u0000\u01d9\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001\u0000"+ "\u0000\u0000\u01dac\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0004\u0000"+ - "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005K\u0000\u0000\u01de"+ + "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005J\u0000\u0000\u01de"+ "\u01e0\u0003.\u0017\u0000\u01df\u01dd\u0001\u0000\u0000\u0000\u01df\u01e0"+ "\u0001\u0000\u0000\u0000\u01e0\u01e6\u0001\u0000\u0000\u0000\u01e1\u01e2"+ - "\u00059\u0000\u0000\u01e2\u01e3\u0003.\u0017\u0000\u01e3\u01e4\u0005?"+ - "\u0000\u0000\u01e4\u01e5\u0003.\u0017\u0000\u01e5\u01e7\u0001\u0000\u0000"+ + "\u0005\u0084\u0000\u0000\u01e2\u01e3\u0003.\u0017\u0000\u01e3\u01e4\u0005"+ + ">\u0000\u0000\u01e4\u01e5\u0003.\u0017\u0000\u01e5\u01e7\u0001\u0000\u0000"+ "\u0000\u01e6\u01e1\u0001\u0000\u0000\u0000\u01e6\u01e7\u0001\u0000\u0000"+ "\u0000\u01e7e\u0001\u0000\u0000\u0000\u01e8\u01e9\u0005\u001e\u0000\u0000"+ "\u01e9\u01ea\u00032\u0019\u0000\u01eag\u0001\u0000\u0000\u0000\u01eb\u01ec"+ @@ -7325,8 +7324,8 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u0000\u0000\u01ee\u01f0\u0003l6\u0000\u01ef\u01ee\u0001\u0000\u0000\u0000"+ "\u01f0\u01f1\u0001\u0000\u0000\u0000\u01f1\u01ef\u0001\u0000\u0000\u0000"+ "\u01f1\u01f2\u0001\u0000\u0000\u0000\u01f2k\u0001\u0000\u0000\u0000\u01f3"+ - "\u01f4\u0005d\u0000\u0000\u01f4\u01f5\u0003n7\u0000\u01f5\u01f6\u0005"+ - "e\u0000\u0000\u01f6m\u0001\u0000\u0000\u0000\u01f7\u01f8\u00067\uffff"+ + "\u01f4\u0005c\u0000\u0000\u01f4\u01f5\u0003n7\u0000\u01f5\u01f6\u0005"+ + "d\u0000\u0000\u01f6m\u0001\u0000\u0000\u0000\u01f7\u01f8\u00067\uffff"+ "\uffff\u0000\u01f8\u01f9\u0003p8\u0000\u01f9\u01ff\u0001\u0000\u0000\u0000"+ "\u01fa\u01fb\n\u0001\u0000\u0000\u01fb\u01fc\u00054\u0000\u0000\u01fc"+ "\u01fe\u0003p8\u0000\u01fd\u01fa\u0001\u0000\u0000\u0000\u01fe\u0201\u0001"+ @@ -7340,146 +7339,147 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u0000\u0000\u0208\u0207\u0001\u0000\u0000\u0000\u0209q\u0001\u0000\u0000"+ "\u0000\u020a\u020b\u0005\u001f\u0000\u0000\u020bs\u0001\u0000\u0000\u0000"+ "\u020c\u020d\u0005\u0011\u0000\u0000\u020d\u020e\u0003\u008eG\u0000\u020e"+ - "\u020f\u0005K\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ - "\u0005P\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ + "\u020f\u0005J\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ + "\u0005O\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ "\u0000\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213u\u0001\u0000\u0000"+ - "\u0000\u0214\u0215\u0005\u0007\u0000\u0000\u0215\u0216\u0003\u0084B\u0000"+ - "\u0216\u0217\u0005P\u0000\u0000\u0217\u021a\u0003<\u001e\u0000\u0218\u0219"+ - "\u00059\u0000\u0000\u0219\u021b\u0003.\u0017\u0000\u021a\u0218\u0001\u0000"+ - "\u0000\u0000\u021a\u021b\u0001\u0000\u0000\u0000\u021bw\u0001\u0000\u0000"+ - "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021e\u0003\u0094J\u0000"+ - "\u021ey\u0001\u0000\u0000\u0000\u021f\u0220\u0006=\uffff\uffff\u0000\u0220"+ - "\u0221\u0005H\u0000\u0000\u0221\u023d\u0003z=\b\u0222\u023d\u0003\u0080"+ - "@\u0000\u0223\u023d\u0003|>\u0000\u0224\u0226\u0003\u0080@\u0000\u0225"+ - "\u0227\u0005H\u0000\u0000\u0226\u0225\u0001\u0000\u0000\u0000\u0226\u0227"+ - "\u0001\u0000\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228\u0229"+ - "\u0005D\u0000\u0000\u0229\u022a\u0005d\u0000\u0000\u022a\u022f\u0003\u0080"+ - "@\u0000\u022b\u022c\u0005?\u0000\u0000\u022c\u022e\u0003\u0080@\u0000"+ - "\u022d\u022b\u0001\u0000\u0000\u0000\u022e\u0231\u0001\u0000\u0000\u0000"+ - "\u022f\u022d\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000"+ - "\u0230\u0232\u0001\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000"+ - "\u0232\u0233\u0005e\u0000\u0000\u0233\u023d\u0001\u0000\u0000\u0000\u0234"+ - "\u0235\u0003\u0080@\u0000\u0235\u0237\u0005E\u0000\u0000\u0236\u0238\u0005"+ - "H\u0000\u0000\u0237\u0236\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000"+ - "\u0000\u0000\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0005I\u0000"+ - "\u0000\u023a\u023d\u0001\u0000\u0000\u0000\u023b\u023d\u0003~?\u0000\u023c"+ - "\u021f\u0001\u0000\u0000\u0000\u023c\u0222\u0001\u0000\u0000\u0000\u023c"+ - "\u0223\u0001\u0000\u0000\u0000\u023c\u0224\u0001\u0000\u0000\u0000\u023c"+ - "\u0234\u0001\u0000\u0000\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d"+ - "\u0246\u0001\u0000\u0000\u0000\u023e\u023f\n\u0005\u0000\u0000\u023f\u0240"+ - "\u00058\u0000\u0000\u0240\u0245\u0003z=\u0006\u0241\u0242\n\u0004\u0000"+ - "\u0000\u0242\u0243\u0005L\u0000\u0000\u0243\u0245\u0003z=\u0005\u0244"+ - "\u023e\u0001\u0000\u0000\u0000\u0244\u0241\u0001\u0000\u0000\u0000\u0245"+ - "\u0248\u0001\u0000\u0000\u0000\u0246\u0244\u0001\u0000\u0000\u0000\u0246"+ - "\u0247\u0001\u0000\u0000\u0000\u0247{\u0001\u0000\u0000\u0000\u0248\u0246"+ - "\u0001\u0000\u0000\u0000\u0249\u024b\u0003\u0080@\u0000\u024a\u024c\u0005"+ - "H\u0000\u0000\u024b\u024a\u0001\u0000\u0000\u0000\u024b\u024c\u0001\u0000"+ - "\u0000\u0000\u024c\u024d\u0001\u0000\u0000\u0000\u024d\u024e\u0005G\u0000"+ - "\u0000\u024e\u024f\u0003\u0098L\u0000\u024f\u0258\u0001\u0000\u0000\u0000"+ - "\u0250\u0252\u0003\u0080@\u0000\u0251\u0253\u0005H\u0000\u0000\u0252\u0251"+ - "\u0001\u0000\u0000\u0000\u0252\u0253\u0001\u0000\u0000\u0000\u0253\u0254"+ - "\u0001\u0000\u0000\u0000\u0254\u0255\u0005N\u0000\u0000\u0255\u0256\u0003"+ - "\u0098L\u0000\u0256\u0258\u0001\u0000\u0000\u0000\u0257\u0249\u0001\u0000"+ - "\u0000\u0000\u0257\u0250\u0001\u0000\u0000\u0000\u0258}\u0001\u0000\u0000"+ - "\u0000\u0259\u025c\u0003.\u0017\u0000\u025a\u025b\u0005=\u0000\u0000\u025b"+ - "\u025d\u0003\n\u0005\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025c\u025d"+ - "\u0001\u0000\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f"+ - "\u0005>\u0000\u0000\u025f\u0260\u0003\u008eG\u0000\u0260\u007f\u0001\u0000"+ - "\u0000\u0000\u0261\u0267\u0003\u0082A\u0000\u0262\u0263\u0003\u0082A\u0000"+ - "\u0263\u0264\u0003\u009aM\u0000\u0264\u0265\u0003\u0082A\u0000\u0265\u0267"+ - "\u0001\u0000\u0000\u0000\u0266\u0261\u0001\u0000\u0000\u0000\u0266\u0262"+ - "\u0001\u0000\u0000\u0000\u0267\u0081\u0001\u0000\u0000\u0000\u0268\u0269"+ - "\u0006A\uffff\uffff\u0000\u0269\u026d\u0003\u0084B\u0000\u026a\u026b\u0007"+ - "\u0004\u0000\u0000\u026b\u026d\u0003\u0082A\u0003\u026c\u0268\u0001\u0000"+ - "\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026d\u0276\u0001\u0000"+ - "\u0000\u0000\u026e\u026f\n\u0002\u0000\u0000\u026f\u0270\u0007\u0005\u0000"+ - "\u0000\u0270\u0275\u0003\u0082A\u0003\u0271\u0272\n\u0001\u0000\u0000"+ - "\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0002\u0274"+ - "\u026e\u0001\u0000\u0000\u0000\u0274\u0271\u0001\u0000\u0000\u0000\u0275"+ - "\u0278\u0001\u0000\u0000\u0000\u0276\u0274\u0001\u0000\u0000\u0000\u0276"+ - "\u0277\u0001\u0000\u0000\u0000\u0277\u0083\u0001\u0000\u0000\u0000\u0278"+ - "\u0276\u0001\u0000\u0000\u0000\u0279\u027a\u0006B\uffff\uffff\u0000\u027a"+ - "\u0282\u0003\u008eG\u0000\u027b\u0282\u0003.\u0017\u0000\u027c\u0282\u0003"+ - "\u0086C\u0000\u027d\u027e\u0005d\u0000\u0000\u027e\u027f\u0003z=\u0000"+ - "\u027f\u0280\u0005e\u0000\u0000\u0280\u0282\u0001\u0000\u0000\u0000\u0281"+ - "\u0279\u0001\u0000\u0000\u0000\u0281\u027b\u0001\u0000\u0000\u0000\u0281"+ - "\u027c\u0001\u0000\u0000\u0000\u0281\u027d\u0001\u0000\u0000\u0000\u0282"+ - "\u0288\u0001\u0000\u0000\u0000\u0283\u0284\n\u0001\u0000\u0000\u0284\u0285"+ - "\u0005=\u0000\u0000\u0285\u0287\u0003\n\u0005\u0000\u0286\u0283\u0001"+ - "\u0000\u0000\u0000\u0287\u028a\u0001\u0000\u0000\u0000\u0288\u0286\u0001"+ - "\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000\u0289\u0085\u0001"+ - "\u0000\u0000\u0000\u028a\u0288\u0001\u0000\u0000\u0000\u028b\u028c\u0003"+ - "\u0088D\u0000\u028c\u029a\u0005d\u0000\u0000\u028d\u029b\u0005Z\u0000"+ - "\u0000\u028e\u0293\u0003z=\u0000\u028f\u0290\u0005?\u0000\u0000\u0290"+ - "\u0292\u0003z=\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0295\u0001"+ - "\u0000\u0000\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0293\u0294\u0001"+ - "\u0000\u0000\u0000\u0294\u0298\u0001\u0000\u0000\u0000\u0295\u0293\u0001"+ - "\u0000\u0000\u0000\u0296\u0297\u0005?\u0000\u0000\u0297\u0299\u0003\u008a"+ - "E\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0298\u0299\u0001\u0000\u0000"+ - "\u0000\u0299\u029b\u0001\u0000\u0000\u0000\u029a\u028d\u0001\u0000\u0000"+ - "\u0000\u029a\u028e\u0001\u0000\u0000\u0000\u029a\u029b\u0001\u0000\u0000"+ - "\u0000\u029b\u029c\u0001\u0000\u0000\u0000\u029c\u029d\u0005e\u0000\u0000"+ - "\u029d\u0087\u0001\u0000\u0000\u0000\u029e\u029f\u0003<\u001e\u0000\u029f"+ - "\u0089\u0001\u0000\u0000\u0000\u02a0\u02a1\u0005]\u0000\u0000\u02a1\u02a6"+ - "\u0003\u008cF\u0000\u02a2\u02a3\u0005?\u0000\u0000\u02a3\u02a5\u0003\u008c"+ - "F\u0000\u02a4\u02a2\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\u02aa\u0005^\u0000\u0000\u02aa\u008b\u0001\u0000\u0000\u0000"+ - "\u02ab\u02ac\u0003\u0098L\u0000\u02ac\u02ad\u0005>\u0000\u0000\u02ad\u02ae"+ - "\u0003\u008eG\u0000\u02ae\u008d\u0001\u0000\u0000\u0000\u02af\u02da\u0005"+ - "I\u0000\u0000\u02b0\u02b1\u0003\u0096K\u0000\u02b1\u02b2\u0005f\u0000"+ - "\u0000\u02b2\u02da\u0001\u0000\u0000\u0000\u02b3\u02da\u0003\u0094J\u0000"+ - "\u02b4\u02da\u0003\u0096K\u0000\u02b5\u02da\u0003\u0090H\u0000\u02b6\u02da"+ - "\u00038\u001c\u0000\u02b7\u02da\u0003\u0098L\u0000\u02b8\u02b9\u0005b"+ - "\u0000\u0000\u02b9\u02be\u0003\u0092I\u0000\u02ba\u02bb\u0005?\u0000\u0000"+ - "\u02bb\u02bd\u0003\u0092I\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd"+ - "\u02c0\u0001\u0000\u0000\u0000\u02be\u02bc\u0001\u0000\u0000\u0000\u02be"+ - "\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c1\u0001\u0000\u0000\u0000\u02c0"+ - "\u02be\u0001\u0000\u0000\u0000\u02c1\u02c2\u0005c\u0000\u0000\u02c2\u02da"+ - "\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000\u02c4\u02c9\u0003"+ - "\u0090H\u0000\u02c5\u02c6\u0005?\u0000\u0000\u02c6\u02c8\u0003\u0090H"+ - "\u0000\u02c7\u02c5\u0001\u0000\u0000\u0000\u02c8\u02cb\u0001\u0000\u0000"+ - "\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000"+ - "\u0000\u02ca\u02cc\u0001\u0000\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000"+ - "\u0000\u02cc\u02cd\u0005c\u0000\u0000\u02cd\u02da\u0001\u0000\u0000\u0000"+ - "\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02d4\u0003\u0098L\u0000\u02d0\u02d1"+ - "\u0005?\u0000\u0000\u02d1\u02d3\u0003\u0098L\u0000\u02d2\u02d0\u0001\u0000"+ - "\u0000\u0000\u02d3\u02d6\u0001\u0000\u0000\u0000\u02d4\u02d2\u0001\u0000"+ - "\u0000\u0000\u02d4\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d7\u0001\u0000"+ - "\u0000\u0000\u02d6\u02d4\u0001\u0000\u0000\u0000\u02d7\u02d8\u0005c\u0000"+ - "\u0000\u02d8\u02da\u0001\u0000\u0000\u0000\u02d9\u02af\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b0\u0001\u0000\u0000\u0000\u02d9\u02b3\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b4\u0001\u0000\u0000\u0000\u02d9\u02b5\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b6\u0001\u0000\u0000\u0000\u02d9\u02b7\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b8\u0001\u0000\u0000\u0000\u02d9\u02c3\u0001\u0000\u0000"+ - "\u0000\u02d9\u02ce\u0001\u0000\u0000\u0000\u02da\u008f\u0001\u0000\u0000"+ - "\u0000\u02db\u02dc\u0007\u0006\u0000\u0000\u02dc\u0091\u0001\u0000\u0000"+ - "\u0000\u02dd\u02e0\u0003\u0094J\u0000\u02de\u02e0\u0003\u0096K\u0000\u02df"+ - "\u02dd\u0001\u0000\u0000\u0000\u02df\u02de\u0001\u0000\u0000\u0000\u02e0"+ - "\u0093\u0001\u0000\u0000\u0000\u02e1\u02e3\u0007\u0004\u0000\u0000\u02e2"+ - "\u02e1\u0001\u0000\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ - "\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u00057\u0000\u0000\u02e5\u0095"+ - "\u0001\u0000\u0000\u0000\u02e6\u02e8\u0007\u0004\u0000\u0000\u02e7\u02e6"+ - "\u0001\u0000\u0000\u0000\u02e7\u02e8\u0001\u0000\u0000\u0000\u02e8\u02e9"+ - "\u0001\u0000\u0000\u0000\u02e9\u02ea\u00056\u0000\u0000\u02ea\u0097\u0001"+ - "\u0000\u0000\u0000\u02eb\u02ec\u00055\u0000\u0000\u02ec\u0099\u0001\u0000"+ - "\u0000\u0000\u02ed\u02ee\u0007\u0007\u0000\u0000\u02ee\u009b\u0001\u0000"+ - "\u0000\u0000\u02ef\u02f0\u0007\b\u0000\u0000\u02f0\u02f1\u0005s\u0000"+ - "\u0000\u02f1\u02f2\u0003\u009eO\u0000\u02f2\u02f3\u0003\u00a0P\u0000\u02f3"+ - "\u009d\u0001\u0000\u0000\u0000\u02f4\u02f5\u0003\u001c\u000e\u0000\u02f5"+ - "\u009f\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005K\u0000\u0000\u02f7\u02fc"+ - "\u0003\u00a2Q\u0000\u02f8\u02f9\u0005?\u0000\u0000\u02f9\u02fb\u0003\u00a2"+ - "Q\u0000\u02fa\u02f8\u0001\u0000\u0000\u0000\u02fb\u02fe\u0001\u0000\u0000"+ - "\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fc\u02fd\u0001\u0000\u0000"+ - "\u0000\u02fd\u00a1\u0001\u0000\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000"+ - "\u0000\u02ff\u0300\u0003\u0080@\u0000\u0300\u00a3\u0001\u0000\u0000\u0000"+ - "F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117\u0119"+ - "\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167\u016c"+ - "\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9\u01df"+ - "\u01e6\u01f1\u01ff\u0208\u0212\u021a\u0226\u022f\u0237\u023c\u0244\u0246"+ - "\u024b\u0252\u0257\u025c\u0266\u026c\u0274\u0276\u0281\u0288\u0293\u0298"+ - "\u029a\u02a6\u02be\u02c9\u02d4\u02d9\u02df\u02e2\u02e7\u02fc"; + "\u0000\u0214\u0218\u0005\u0007\u0000\u0000\u0215\u0216\u0003.\u0017\u0000"+ + "\u0216\u0217\u0005:\u0000\u0000\u0217\u0219\u0001\u0000\u0000\u0000\u0218"+ + "\u0215\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219"+ + "\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0003\u0084B\u0000\u021b\u021c"+ + "\u0005O\u0000\u0000\u021c\u021d\u0003<\u001e\u0000\u021dw\u0001\u0000"+ + "\u0000\u0000\u021e\u021f\u0005\u0012\u0000\u0000\u021f\u0220\u0003\u0094"+ + "J\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222\u0006=\uffff\uffff"+ + "\u0000\u0222\u0223\u0005G\u0000\u0000\u0223\u023f\u0003z=\b\u0224\u023f"+ + "\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228\u0003\u0080"+ + "@\u0000\u0227\u0229\u0005G\u0000\u0000\u0228\u0227\u0001\u0000\u0000\u0000"+ + "\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000\u0000\u0000"+ + "\u022a\u022b\u0005C\u0000\u0000\u022b\u022c\u0005c\u0000\u0000\u022c\u0231"+ + "\u0003\u0080@\u0000\u022d\u022e\u0005>\u0000\u0000\u022e\u0230\u0003\u0080"+ + "@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001\u0000\u0000"+ + "\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001\u0000\u0000"+ + "\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001\u0000\u0000"+ + "\u0000\u0234\u0235\u0005d\u0000\u0000\u0235\u023f\u0001\u0000\u0000\u0000"+ + "\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005D\u0000\u0000\u0238\u023a"+ + "\u0005G\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239\u023a\u0001"+ + "\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0005"+ + "H\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f\u0003~?"+ + "\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000\u0000"+ + "\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000\u0000"+ + "\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000\u0000"+ + "\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000\u0000"+ + "\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243\u0244"+ + "\n\u0004\u0000\u0000\u0244\u0245\u0005K\u0000\u0000\u0245\u0247\u0003"+ + "z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000\u0000"+ + "\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000"+ + "\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000\u0000"+ + "\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000\u024c"+ + "\u024e\u0005G\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e"+ + "\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250"+ + "\u0005F\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001\u0000"+ + "\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005G\u0000\u0000"+ + "\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000"+ + "\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005M\u0000\u0000\u0257"+ + "\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259\u024b"+ + "\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a}\u0001"+ + "\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005<\u0000"+ + "\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000\u0000\u0000"+ + "\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000\u0000\u0000"+ + "\u0260\u0261\u0005=\u0000\u0000\u0261\u0262\u0003\u008eG\u0000\u0262\u007f"+ + "\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264\u0265\u0003"+ + "\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003\u0082A"+ + "\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000\u0000"+ + "\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000\u0000"+ + "\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084B\u0000"+ + "\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003\u026e"+ + "\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ + "\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271\u0272"+ + "\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274\n"+ + "\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ + "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ + "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ + "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ + "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ + "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ + "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005c\u0000\u0000\u0280\u0281"+ + "\u0003z=\u0000\u0281\u0282\u0005d\u0000\u0000\u0282\u0284\u0001\u0000"+ + "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ + "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ + "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ + "\u0000\u0286\u0287\u0005<\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ + "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ + "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ + "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ + "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005c\u0000\u0000\u028f\u029d"+ + "\u0005Y\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005>\u0000"+ + "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ + "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ + "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ + "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005>\u0000\u0000\u0299\u029b"+ + "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ + "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ + "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ + "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ + "d\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ + "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005\\\u0000\u0000"+ + "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005>\u0000\u0000\u02a5\u02a7"+ + "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ + "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ + "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ + "\u0000\u0000\u0000\u02ab\u02ac\u0005]\u0000\u0000\u02ac\u008b\u0001\u0000"+ + "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005=\u0000\u0000"+ + "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ + "\u02dc\u0005H\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ + "e\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ + "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ + "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ + "\u0005a\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005>"+ + "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ + "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ + "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ + "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000"+ + "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005a\u0000\u0000\u02c6"+ + "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005>\u0000\u0000\u02c8\u02ca\u0003"+ + "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ + "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ + "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ + "\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ + "\u0000\u02d0\u02d1\u0005a\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ + "\u02d3\u0005>\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ + "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ + "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ + "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ + "b\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ + "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ + "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ + "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ + "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ + "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ + "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ + "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ + "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ + "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ + "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ + "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ + "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ + "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ + "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ + "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ + "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ + "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005r"+ + "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ + "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ + "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005J\u0000\u0000\u02f9"+ + "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005>\u0000\u0000\u02fb\u02fd\u0003"+ + "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ + "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ + "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ + "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ + "\u0000F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ + "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ + "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ + "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u0218\u0228\u0231\u0239\u023e\u0246"+ + "\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a\u0295"+ + "\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index 19fb563488e61..df4cc64759676 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -3776,7 +3776,7 @@ public void testResolveCompletionInferenceIdResolutionError() { public void testResolveCompletionTargetField() { LogicalPlan plan = analyze(""" FROM books METADATA _score - | COMPLETION CONCAT("Translate the following text in French\\n", description) WITH `completion-inference-id` AS translation + | COMPLETION translation=CONCAT("Translate the following text in French\\n", description) WITH `completion-inference-id` """, "mapping-books.json"); Completion completion = as(as(plan, Limit.class).child(), Completion.class); @@ -3818,7 +3818,7 @@ public void testResolveCompletionPromptInvalidType() { public void testResolveCompletionOutputField() { LogicalPlan plan = analyze(""" FROM books METADATA _score - | COMPLETION CONCAT("Translate the following text in French\\n", description) WITH `completion-inference-id` AS description + | COMPLETION description=CONCAT("Translate the following text in French\\n", description) WITH `completion-inference-id` """, "mapping-books.json"); Completion completion = as(as(plan, Limit.class).child(), Completion.class); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index 635abae133d20..c01da28c59e2b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -5646,7 +5646,7 @@ record PushdownShadowingGeneratingPlanTestCase( ), new PushDownEnrich() ), - // | COMPLETION CONCAT(some text, x) WITH inferenceID AS y + // | COMPLETION y=CONCAT(some text, x) WITH inferenceID new PushdownShadowingGeneratingPlanTestCase( (plan, attr) -> new Completion( EMPTY, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java index ef3cacfb0e471..7bf8256ccd9f6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java @@ -245,8 +245,8 @@ public void testSelectivelyPushDownFilterPastFunctionAgg() { assertEquals(expected, new PushDownAndCombineFilters().apply(fb)); } - // from ... | where a > 1 | COMPLETION "some prompt" WITH reranker AS completion | where b < 2 and match(completion, some text) - // => ... | where a > 1 AND b < 2| COMPLETION "some prompt" WITH reranker AS completion | match(completion, some text) + // from ... | where a > 1 | COMPLETION completion="some prompt" WITH reranker | where b < 2 and match(completion, some text) + // => ... | where a > 1 AND b < 2| COMPLETION completion="some prompt" WITH reranker | match(completion, some text) public void testPushDownFilterPastCompletion() { FieldAttribute a = getFieldAttribute("a"); FieldAttribute b = getFieldAttribute("b"); 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 60851771dad8b..11733b7c4723d 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 @@ -3432,7 +3432,7 @@ public void testInvalidRerank() { } public void testCompletionUsingFieldAsPrompt() { - var plan = as(processingCommand("COMPLETION prompt_field WITH inferenceID AS targetField"), Completion.class); + var plan = as(processingCommand("COMPLETION targetField=prompt_field WITH inferenceID"), Completion.class); assertThat(plan.prompt(), equalTo(attribute("prompt_field"))); assertThat(plan.inferenceId(), equalTo(literalString("inferenceID"))); @@ -3440,7 +3440,7 @@ public void testCompletionUsingFieldAsPrompt() { } public void testCompletionUsingFunctionAsPrompt() { - var plan = as(processingCommand("COMPLETION CONCAT(fieldA, fieldB) WITH inferenceID AS targetField"), Completion.class); + var plan = as(processingCommand("COMPLETION targetField=CONCAT(fieldA, fieldB) WITH inferenceID"), Completion.class); assertThat(plan.prompt(), equalTo(function("CONCAT", List.of(attribute("fieldA"), attribute("fieldB"))))); assertThat(plan.inferenceId(), equalTo(literalString("inferenceID"))); @@ -3476,9 +3476,9 @@ public void testCompletionWithNamedParameters() { public void testInvalidCompletion() { expectError("FROM foo* | COMPLETION WITH inferenceId", "line 1:24: extraneous input 'WITH' expecting {"); - expectError("FROM foo* | COMPLETION prompt WITH", "line 1:35: mismatched input '' expecting {"); + expectError("FROM foo* | COMPLETION completion=prompt WITH", "line 1:46: mismatched input '' expecting {"); - expectError("FROM foo* | COMPLETION prompt AS targetField", "line 1:31: mismatched input 'AS' expecting {"); + expectError("FROM foo* | COMPLETION completion=prompt", "line 1:41: mismatched input '' expecting {"); } public void testSample() { From efc64509ff3200d72c0c5b41109faff2b25394fc Mon Sep 17 00:00:00 2001 From: Jan Kuipers <148754765+jan-elastic@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:19:44 +0200 Subject: [PATCH 040/102] Remove optional seed from ES|QL SAMPLE (#128887) * Remove optional seed from ES|QL SAMPLE * make it clear that seed is for testing --- .../xpack/esql/parser/EsqlBaseParser.interp | 2 +- .../xpack/esql/parser/EsqlBaseParser.java | 1084 +++++++++-------- 2 files changed, 552 insertions(+), 534 deletions(-) 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 7638709eb9577..02f18d8fa1726 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 @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 537, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 803, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 542, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 62, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 58, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 98, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 74, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 79, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 62, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 58, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 74, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 59, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 74, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 132, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 62, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 99, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 100, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 74, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 79, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 536, 5, 7, 0, 0, 533, 534, 3, 46, 23, 0, 534, 535, 5, 58, 0, 0, 535, 537, 1, 0, 0, 0, 536, 533, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 3, 132, 66, 0, 539, 540, 5, 79, 0, 0, 540, 541, 3, 60, 30, 0, 541, 119, 1, 0, 0, 0, 542, 543, 5, 18, 0, 0, 543, 544, 3, 148, 74, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 71, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 71, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 67, 0, 0, 555, 556, 5, 99, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 62, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 100, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 68, 0, 0, 568, 570, 5, 71, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 72, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 75, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 71, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 70, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 71, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 77, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 60, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 61, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 99, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 100, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 60, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 99, 0, 0, 655, 669, 5, 89, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 62, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 62, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 100, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 92, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 62, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 93, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 61, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 72, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 101, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 97, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 62, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 97, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 62, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 97, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 62, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 98, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 114, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 74, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 62, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 536, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ No newline at end of file +[4, 1, 139, 770, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 551, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 558, 8, 61, 10, 61, 12, 61, 561, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 568, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 573, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 581, 8, 61, 10, 61, 12, 61, 584, 9, 61, 1, 62, 1, 62, 3, 62, 588, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 595, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 600, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 605, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 615, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 621, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 629, 8, 65, 10, 65, 12, 65, 632, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 642, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 647, 8, 66, 10, 66, 12, 66, 650, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 658, 8, 67, 10, 67, 12, 67, 661, 9, 67, 1, 67, 1, 67, 3, 67, 665, 8, 67, 3, 67, 667, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 677, 8, 69, 10, 69, 12, 69, 680, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 701, 8, 71, 10, 71, 12, 71, 704, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 712, 8, 71, 10, 71, 12, 71, 715, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 723, 8, 71, 10, 71, 12, 71, 726, 9, 71, 1, 71, 1, 71, 3, 71, 730, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 736, 8, 73, 1, 74, 3, 74, 739, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 744, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 763, 8, 80, 10, 80, 12, 80, 766, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 801, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 572, 1, 0, 0, 0, 124, 599, 1, 0, 0, 0, 126, 601, 1, 0, 0, 0, 128, 614, 1, 0, 0, 0, 130, 620, 1, 0, 0, 0, 132, 641, 1, 0, 0, 0, 134, 651, 1, 0, 0, 0, 136, 670, 1, 0, 0, 0, 138, 672, 1, 0, 0, 0, 140, 683, 1, 0, 0, 0, 142, 729, 1, 0, 0, 0, 144, 731, 1, 0, 0, 0, 146, 735, 1, 0, 0, 0, 148, 738, 1, 0, 0, 0, 150, 743, 1, 0, 0, 0, 152, 747, 1, 0, 0, 0, 154, 749, 1, 0, 0, 0, 156, 751, 1, 0, 0, 0, 158, 756, 1, 0, 0, 0, 160, 758, 1, 0, 0, 0, 162, 767, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 542, 3, 148, 74, 0, 542, 121, 1, 0, 0, 0, 543, 544, 6, 61, -1, 0, 544, 545, 5, 72, 0, 0, 545, 573, 3, 122, 61, 8, 546, 573, 3, 128, 64, 0, 547, 573, 3, 124, 62, 0, 548, 550, 3, 128, 64, 0, 549, 551, 5, 72, 0, 0, 550, 549, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 5, 68, 0, 0, 553, 554, 5, 100, 0, 0, 554, 559, 3, 128, 64, 0, 555, 556, 5, 63, 0, 0, 556, 558, 3, 128, 64, 0, 557, 555, 1, 0, 0, 0, 558, 561, 1, 0, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 562, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 562, 563, 5, 101, 0, 0, 563, 573, 1, 0, 0, 0, 564, 565, 3, 128, 64, 0, 565, 567, 5, 69, 0, 0, 566, 568, 5, 72, 0, 0, 567, 566, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 5, 73, 0, 0, 570, 573, 1, 0, 0, 0, 571, 573, 3, 126, 63, 0, 572, 543, 1, 0, 0, 0, 572, 546, 1, 0, 0, 0, 572, 547, 1, 0, 0, 0, 572, 548, 1, 0, 0, 0, 572, 564, 1, 0, 0, 0, 572, 571, 1, 0, 0, 0, 573, 582, 1, 0, 0, 0, 574, 575, 10, 5, 0, 0, 575, 576, 5, 56, 0, 0, 576, 581, 3, 122, 61, 6, 577, 578, 10, 4, 0, 0, 578, 579, 5, 76, 0, 0, 579, 581, 3, 122, 61, 5, 580, 574, 1, 0, 0, 0, 580, 577, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 123, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 3, 128, 64, 0, 586, 588, 5, 72, 0, 0, 587, 586, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 5, 71, 0, 0, 590, 591, 3, 152, 76, 0, 591, 600, 1, 0, 0, 0, 592, 594, 3, 128, 64, 0, 593, 595, 5, 72, 0, 0, 594, 593, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 5, 78, 0, 0, 597, 598, 3, 152, 76, 0, 598, 600, 1, 0, 0, 0, 599, 585, 1, 0, 0, 0, 599, 592, 1, 0, 0, 0, 600, 125, 1, 0, 0, 0, 601, 604, 3, 46, 23, 0, 602, 603, 5, 61, 0, 0, 603, 605, 3, 10, 5, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 5, 62, 0, 0, 607, 608, 3, 142, 71, 0, 608, 127, 1, 0, 0, 0, 609, 615, 3, 130, 65, 0, 610, 611, 3, 130, 65, 0, 611, 612, 3, 154, 77, 0, 612, 613, 3, 130, 65, 0, 613, 615, 1, 0, 0, 0, 614, 609, 1, 0, 0, 0, 614, 610, 1, 0, 0, 0, 615, 129, 1, 0, 0, 0, 616, 617, 6, 65, -1, 0, 617, 621, 3, 132, 66, 0, 618, 619, 7, 4, 0, 0, 619, 621, 3, 130, 65, 3, 620, 616, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 630, 1, 0, 0, 0, 622, 623, 10, 2, 0, 0, 623, 624, 7, 5, 0, 0, 624, 629, 3, 130, 65, 3, 625, 626, 10, 1, 0, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 2, 628, 622, 1, 0, 0, 0, 628, 625, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 131, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 6, 66, -1, 0, 634, 642, 3, 142, 71, 0, 635, 642, 3, 46, 23, 0, 636, 642, 3, 134, 67, 0, 637, 638, 5, 100, 0, 0, 638, 639, 3, 122, 61, 0, 639, 640, 5, 101, 0, 0, 640, 642, 1, 0, 0, 0, 641, 633, 1, 0, 0, 0, 641, 635, 1, 0, 0, 0, 641, 636, 1, 0, 0, 0, 641, 637, 1, 0, 0, 0, 642, 648, 1, 0, 0, 0, 643, 644, 10, 1, 0, 0, 644, 645, 5, 61, 0, 0, 645, 647, 3, 10, 5, 0, 646, 643, 1, 0, 0, 0, 647, 650, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 133, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 651, 652, 3, 136, 68, 0, 652, 666, 5, 100, 0, 0, 653, 667, 5, 90, 0, 0, 654, 659, 3, 122, 61, 0, 655, 656, 5, 63, 0, 0, 656, 658, 3, 122, 61, 0, 657, 655, 1, 0, 0, 0, 658, 661, 1, 0, 0, 0, 659, 657, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 664, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 662, 663, 5, 63, 0, 0, 663, 665, 3, 138, 69, 0, 664, 662, 1, 0, 0, 0, 664, 665, 1, 0, 0, 0, 665, 667, 1, 0, 0, 0, 666, 653, 1, 0, 0, 0, 666, 654, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 669, 5, 101, 0, 0, 669, 135, 1, 0, 0, 0, 670, 671, 3, 60, 30, 0, 671, 137, 1, 0, 0, 0, 672, 673, 5, 93, 0, 0, 673, 678, 3, 140, 70, 0, 674, 675, 5, 63, 0, 0, 675, 677, 3, 140, 70, 0, 676, 674, 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, 682, 5, 94, 0, 0, 682, 139, 1, 0, 0, 0, 683, 684, 3, 152, 76, 0, 684, 685, 5, 62, 0, 0, 685, 686, 3, 142, 71, 0, 686, 141, 1, 0, 0, 0, 687, 730, 5, 73, 0, 0, 688, 689, 3, 150, 75, 0, 689, 690, 5, 102, 0, 0, 690, 730, 1, 0, 0, 0, 691, 730, 3, 148, 74, 0, 692, 730, 3, 150, 75, 0, 693, 730, 3, 144, 72, 0, 694, 730, 3, 56, 28, 0, 695, 730, 3, 152, 76, 0, 696, 697, 5, 98, 0, 0, 697, 702, 3, 146, 73, 0, 698, 699, 5, 63, 0, 0, 699, 701, 3, 146, 73, 0, 700, 698, 1, 0, 0, 0, 701, 704, 1, 0, 0, 0, 702, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 705, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 730, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 713, 3, 144, 72, 0, 709, 710, 5, 63, 0, 0, 710, 712, 3, 144, 72, 0, 711, 709, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 99, 0, 0, 717, 730, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 724, 3, 152, 76, 0, 720, 721, 5, 63, 0, 0, 721, 723, 3, 152, 76, 0, 722, 720, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 727, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 728, 5, 99, 0, 0, 728, 730, 1, 0, 0, 0, 729, 687, 1, 0, 0, 0, 729, 688, 1, 0, 0, 0, 729, 691, 1, 0, 0, 0, 729, 692, 1, 0, 0, 0, 729, 693, 1, 0, 0, 0, 729, 694, 1, 0, 0, 0, 729, 695, 1, 0, 0, 0, 729, 696, 1, 0, 0, 0, 729, 707, 1, 0, 0, 0, 729, 718, 1, 0, 0, 0, 730, 143, 1, 0, 0, 0, 731, 732, 7, 6, 0, 0, 732, 145, 1, 0, 0, 0, 733, 736, 3, 148, 74, 0, 734, 736, 3, 150, 75, 0, 735, 733, 1, 0, 0, 0, 735, 734, 1, 0, 0, 0, 736, 147, 1, 0, 0, 0, 737, 739, 7, 4, 0, 0, 738, 737, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 5, 55, 0, 0, 741, 149, 1, 0, 0, 0, 742, 744, 7, 4, 0, 0, 743, 742, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 5, 54, 0, 0, 746, 151, 1, 0, 0, 0, 747, 748, 5, 53, 0, 0, 748, 153, 1, 0, 0, 0, 749, 750, 7, 7, 0, 0, 750, 155, 1, 0, 0, 0, 751, 752, 7, 8, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 3, 158, 79, 0, 754, 755, 3, 160, 80, 0, 755, 157, 1, 0, 0, 0, 756, 757, 3, 28, 14, 0, 757, 159, 1, 0, 0, 0, 758, 759, 5, 75, 0, 0, 759, 764, 3, 162, 81, 0, 760, 761, 5, 63, 0, 0, 761, 763, 3, 162, 81, 0, 762, 760, 1, 0, 0, 0, 763, 766, 1, 0, 0, 0, 764, 762, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 161, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 768, 3, 128, 64, 0, 768, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 550, 559, 567, 572, 580, 582, 587, 594, 599, 604, 614, 620, 628, 630, 641, 648, 659, 664, 666, 678, 702, 713, 724, 729, 735, 738, 743, 764] \ 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 6f3c676e44abc..95f1b1fc3400f 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 @@ -4505,7 +4505,7 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { { setState(542); match(DEV_SAMPLE); - setState(543); + setState(541); ((SampleCommandContext)_localctx).probability = decimalValue(); } } @@ -4721,18 +4721,19 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(574); + setState(572); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { case 1: { _localctx = new LogicalNotContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(544); match(NOT); - setState(547); + setState(545); booleanExpression(8); } break; @@ -4741,7 +4742,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); + setState(546); valueExpression(); } break; @@ -4750,7 +4751,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(549); + setState(547); regexBooleanExpression(); } break; @@ -4759,41 +4760,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(550); + setState(548); valueExpression(); - setState(552); + setState(550); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(551); + setState(549); match(NOT); } } - setState(554); + setState(552); match(IN); - setState(555); + setState(553); match(LP); - setState(556); + setState(554); valueExpression(); - setState(561); + setState(559); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(557); + setState(555); match(COMMA); - setState(558); + setState(556); valueExpression(); } } - setState(563); + setState(561); _errHandler.sync(this); _la = _input.LA(1); } - setState(564); + setState(562); match(RP); } break; @@ -4802,21 +4803,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(566); + setState(564); valueExpression(); - setState(567); + setState(565); match(IS); - setState(569); + setState(567); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(568); + setState(566); match(NOT); } } - setState(571); + setState(569); match(NULL); } break; @@ -4825,33 +4826,35 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(573); + setState(571); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(584); + setState(582); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); + _alt = getInterpreter().adaptivePredict(_input,47,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(582); + setState(580); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: { _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(576); + setState(574); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(577); + setState(575); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(578); + setState(576); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4860,20 +4863,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(579); + setState(577); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(580); + setState(578); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(581); + setState(579); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(586); + setState(584); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); + _alt = getInterpreter().adaptivePredict(_input,47,_ctx); } } } @@ -4926,48 +4930,49 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(601); + setState(599); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(587); + setState(585); valueExpression(); - setState(589); + setState(587); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(588); + setState(586); match(NOT); } } - setState(591); + setState(589); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(592); + setState(590); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(594); + setState(592); valueExpression(); - setState(596); + setState(594); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(595); + setState(593); match(NOT); } } - setState(598); + setState(596); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(599); + setState(597); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5027,23 +5032,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(603); + setState(601); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(606); + setState(604); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(604); + setState(602); match(CAST_OP); - setState(605); + setState(603); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(608); + setState(606); match(COLON); - setState(609); + setState(607); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5127,14 +5132,15 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(616); + setState(614); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(611); + setState(609); operatorExpression(0); } break; @@ -5142,11 +5148,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(612); + setState(610); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(613); + setState(611); comparisonOperator(); - setState(614); + setState(612); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5271,16 +5277,17 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(622); + setState(620); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { case 1: { _localctx = new OperatorExpressionDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(619); + setState(617); primaryExpression(0); } break; @@ -5289,7 +5296,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(620); + setState(618); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5300,31 +5307,33 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(621); + setState(619); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(632); + setState(630); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); + _alt = getInterpreter().adaptivePredict(_input,55,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(630); + setState(628); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { case 1: { _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState)); ((ArithmeticBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_operatorExpression); - setState(624); + setState(622); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(625); + setState(623); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 89)) & ~0x3f) == 0 && ((1L << (_la - 89)) & 7L) != 0)) ) { @@ -5335,7 +5344,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(626); + setState(624); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5344,9 +5353,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(627); + setState(625); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(628); + setState(626); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5357,16 +5366,17 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(629); + setState(627); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(634); + setState(632); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); + _alt = getInterpreter().adaptivePredict(_input,55,_ctx); } } } @@ -5522,16 +5532,17 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(643); + setState(641); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { case 1: { _localctx = new ConstantDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(634); constant(); } break; @@ -5540,7 +5551,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(635); qualifiedName(); } break; @@ -5549,7 +5560,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(638); + setState(636); functionExpression(); } break; @@ -5558,19 +5569,20 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(639); + setState(637); match(LP); - setState(640); + setState(638); booleanExpression(0); - setState(641); + setState(639); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(650); + setState(648); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); + _alt = getInterpreter().adaptivePredict(_input,57,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); @@ -5579,18 +5591,19 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(645); + setState(643); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(646); + setState(644); match(CAST_OP); - setState(647); + setState(645); dataType(); } } } - setState(652); + setState(650); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); + _alt = getInterpreter().adaptivePredict(_input,57,_ctx); } } } @@ -5654,16 +5667,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(653); + setState(651); functionName(); - setState(654); + setState(652); match(LP); - setState(668); + setState(666); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(655); + setState(653); match(ASTERISK); } break; @@ -5686,34 +5699,36 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(656); + setState(654); booleanExpression(0); - setState(661); + setState(659); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); + _alt = getInterpreter().adaptivePredict(_input,58,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(657); + setState(655); match(COMMA); - setState(658); + setState(656); booleanExpression(0); } } } - setState(663); + setState(661); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); + _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } - setState(666); + setState(664); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(664); + setState(662); match(COMMA); - setState(665); + setState(663); mapExpression(); } } @@ -5726,7 +5741,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(670); + setState(668); match(RP); } } @@ -5772,7 +5787,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(670); identifierOrParameter(); } } @@ -5828,27 +5843,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(674); + setState(672); match(LEFT_BRACES); - setState(675); + setState(673); entryExpression(); - setState(680); + setState(678); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(676); + setState(674); match(COMMA); - setState(677); + setState(675); entryExpression(); } } - setState(682); + setState(680); _errHandler.sync(this); _la = _input.LA(1); } - setState(683); + setState(681); match(RIGHT_BRACES); } } @@ -5900,11 +5915,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(685); + setState(683); ((EntryExpressionContext)_localctx).key = string(); - setState(686); + setState(684); match(COLON); - setState(687); + setState(685); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6175,14 +6190,15 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(731); + setState(729); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(689); + setState(687); match(NULL); } break; @@ -6190,9 +6206,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(690); + setState(688); integerValue(); - setState(691); + setState(689); match(UNQUOTED_IDENTIFIER); } break; @@ -6200,7 +6216,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(693); + setState(691); decimalValue(); } break; @@ -6208,7 +6224,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(694); + setState(692); integerValue(); } break; @@ -6216,7 +6232,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(695); + setState(693); booleanValue(); } break; @@ -6224,7 +6240,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(696); + setState(694); parameter(); } break; @@ -6232,7 +6248,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(697); + setState(695); string(); } break; @@ -6240,27 +6256,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(698); + setState(696); match(OPENING_BRACKET); - setState(699); + setState(697); numericValue(); - setState(704); + setState(702); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(700); + setState(698); match(COMMA); - setState(701); + setState(699); numericValue(); } } - setState(706); + setState(704); _errHandler.sync(this); _la = _input.LA(1); } - setState(707); + setState(705); match(CLOSING_BRACKET); } break; @@ -6268,27 +6284,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(709); + setState(707); match(OPENING_BRACKET); - setState(710); + setState(708); booleanValue(); - setState(715); + setState(713); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(711); + setState(709); match(COMMA); - setState(712); + setState(710); booleanValue(); } } - setState(717); + setState(715); _errHandler.sync(this); _la = _input.LA(1); } - setState(718); + setState(716); match(CLOSING_BRACKET); } break; @@ -6296,27 +6312,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(720); + setState(718); match(OPENING_BRACKET); - setState(721); + setState(719); string(); - setState(726); + setState(724); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(722); + setState(720); match(COMMA); - setState(723); + setState(721); string(); } } - setState(728); + setState(726); _errHandler.sync(this); _la = _input.LA(1); } - setState(729); + setState(727); match(CLOSING_BRACKET); } break; @@ -6364,7 +6380,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(733); + setState(731); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6419,20 +6435,21 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(737); + setState(735); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(735); + setState(733); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(736); + setState(734); integerValue(); } break; @@ -6481,12 +6498,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(740); + setState(738); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(739); + setState(737); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6499,7 +6516,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(742); + setState(740); match(DECIMAL_LITERAL); } } @@ -6546,12 +6563,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(745); + setState(743); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(744); + setState(742); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6564,7 +6581,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(747); + setState(745); match(INTEGER_LITERAL); } } @@ -6608,7 +6625,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(747); match(QUOTED_STRING); } } @@ -6658,7 +6675,7 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(749); _la = _input.LA(1); if ( !(((((_la - 80)) & ~0x3f) == 0 && ((1L << (_la - 80)) & 125L) != 0)) ) { _errHandler.recoverInline(this); @@ -6721,7 +6738,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(753); + setState(751); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6732,11 +6749,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(754); + setState(752); match(JOIN); - setState(755); + setState(753); joinTarget(); - setState(756); + setState(754); joinCondition(); } } @@ -6783,7 +6800,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(758); + setState(756); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6838,27 +6855,29 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(760); + setState(758); match(ON); - setState(761); + setState(759); joinPredicate(); - setState(766); + setState(764); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); + _alt = getInterpreter().adaptivePredict(_input,69,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(762); + setState(760); match(COMMA); - setState(763); + setState(761); joinPredicate(); } } } - setState(768); + setState(766); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); + _alt = getInterpreter().adaptivePredict(_input,69,_ctx); } } } @@ -6904,7 +6923,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(769); + setState(767); valueExpression(); } } @@ -7005,7 +7024,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0304\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u0302\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"+ @@ -7073,243 +7092,243 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "5\u01f0\b5\u000b5\f5\u01f1\u00016\u00016\u00016\u00016\u00017\u00017\u0001"+ "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ - ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0003"+ - ";\u0219\b;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001<\u0001<\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ - ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ - ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ - "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ - "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ - "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ - "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ - "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ - "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ - "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ - "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ - "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ - "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ - "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ - "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ - "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ - "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ - "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ - "5kk\u0001\u0000ef\u0002\u000099??\u0002\u0000BBEE\u0001\u0000WX\u0001"+ - "\u0000Y[\u0002\u0000AANN\u0002\u0000PPRV\u0002\u0000\u0016\u0016\u0018"+ - "\u0019\u0323\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ - "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ - "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ - "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ - "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ - "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ - "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ - "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ - " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ - "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ - "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ - ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ - "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ - "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ - "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ - "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ - "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ - "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ - "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ - "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ - "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ - "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ - "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ - "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ - "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ - "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ - "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021e"+ - "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ - "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ - "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ - "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ - "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ - "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ - "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ - "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ - "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ - "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ - "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ - "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ - "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ - "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ - "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ - "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ - "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ - "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ - "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ - "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ - "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ - "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ - "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ - "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ - " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ - "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ - "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ - "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ - "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ - "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ - "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ - "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ - "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ - "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ - "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ - "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ - "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ - "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ - "\u0000\u00e2\u00e3\u0005>\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ - "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ - "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ - "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005:\u0000\u0000\u00eb\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ - "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ - "\n\u0000\u00f1\u00f2\u0005>\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ - "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ - "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ - "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005:\u0000\u0000\u00fa\u00fc"+ - "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ - "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ - "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ - "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ - "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ - "\u0104\u0105\u0005>\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ - "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ - "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ - "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ - "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ - "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ - "\u0003\u001e\u000f\u0000\u010f\u0110\u0005=\u0000\u0000\u0110\u0112\u0001"+ - "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ - "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ - "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005<\u0000"+ - "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ - "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ - "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ - "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ - "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ - "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ - "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005j\u0000\u0000\u0122\u0127\u0005"+ - "k\u0000\u0000\u0123\u0124\u0005>\u0000\u0000\u0124\u0126\u0005k\u0000"+ - "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ - "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ - "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ - "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ - "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ - "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ - "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ - ";\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ - "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ - "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005>\u0000\u0000\u0137"+ - "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ - "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ - "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ - "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ - "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ - "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ - "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005@\u0000\u0000\u0144\u0146\u0003"+ - "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ - "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ - "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ - "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005@\u0000\u0000\u014c"+ - "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ - "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005>\u0000"+ - "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ - "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ - "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ - "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ - "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160"+ - "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ - "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ - "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000"+ - "\u0162\u0164\u0005_\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ - "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ - "\u0005^\u0000\u0000\u0166\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000"+ - "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ - "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ - "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ - "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ - "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ - "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ - "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174"+ - "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ - "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ - "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ - "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ - "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ - "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f"+ - "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ - "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ - "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ - "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ - "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ - "\u018e\u0003J%\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003"+ - "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ - "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ - "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ - "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193"+ - "\u0194\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005"+ - "\b\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098"+ - "L\u0000\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000"+ - "\u0199\u019a\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b"+ - "\u019c\u0005\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e"+ - "\u0003\u0098L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b"+ - "\u0000\u0000\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000"+ - "\u01a2\u01a7\u0003T*\u0000\u01a3\u01a4\u0005>\u0000\u0000\u01a4\u01a6"+ - "\u0003T*\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\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000"+ - "\u0000\u01aa\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005:\u0000\u0000\u01ac"+ - "\u01ad\u0003\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005"+ - "\u0006\u0000\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000"+ - "\u0000\u01b1\u01b2\u0005a\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000"+ - "\u01b3\u01b4\u0005b\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6"+ - "\u0005!\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000"+ - "\u0000\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000"+ - "\u0000\u01ba\u01bb\u0005J\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc"+ - "\u01ba\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd"+ - "\u01c7\u0001\u0000\u0000\u0000\u01be\u01bf\u0005O\u0000\u0000\u01bf\u01c4"+ - "\u0003^/\u0000\u01c0\u01c1\u0005>\u0000\u0000\u01c1\u01c3\u0003^/\u0000"+ - "\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000"+ - "\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000"+ - "\u01c5\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000"+ - "\u01c7\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000"+ - "\u01c8]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb"+ - "\u0005:\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001"+ - "\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001"+ - "\u0000\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000"+ - "\u0000\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e"+ - "\u0000\u01d2\u01d3\u0005J\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4"+ - "a\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ - "\u0003*\u0015\u0000\u01d7\u01d8\u0005;\u0000\u0000\u01d8\u01da\u0003\u000e"+ + ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0001"+ + ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0003=\u0227\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ + "=\u0005=\u022e\b=\n=\f=\u0231\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003"+ + "=\u0238\b=\u0001=\u0001=\u0001=\u0003=\u023d\b=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0005=\u0245\b=\n=\f=\u0248\t=\u0001>\u0001>\u0003>\u024c"+ + "\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0253\b>\u0001>\u0001>\u0001"+ + ">\u0003>\u0258\b>\u0001?\u0001?\u0001?\u0003?\u025d\b?\u0001?\u0001?\u0001"+ + "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0267\b@\u0001A\u0001A\u0001"+ + "A\u0001A\u0003A\u026d\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ + "A\u0275\bA\nA\fA\u0278\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0003B\u0282\bB\u0001B\u0001B\u0001B\u0005B\u0287\bB\nB\fB\u028a"+ + "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0292\bC\nC\fC\u0295"+ + "\tC\u0001C\u0001C\u0003C\u0299\bC\u0003C\u029b\bC\u0001C\u0001C\u0001"+ + "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a5\bE\nE\fE\u02a8\tE\u0001"+ + "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02bd"+ + "\bG\nG\fG\u02c0\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c8"+ + "\bG\nG\fG\u02cb\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d3"+ + "\bG\nG\fG\u02d6\tG\u0001G\u0001G\u0003G\u02da\bG\u0001H\u0001H\u0001I"+ + "\u0001I\u0003I\u02e0\bI\u0001J\u0003J\u02e3\bJ\u0001J\u0001J\u0001K\u0003"+ + "K\u02e8\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ + "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u02fb"+ + "\bP\nP\fP\u02fe\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ + "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ + "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ + "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ + "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055ll\u0001\u0000fg\u0002\u0000"+ + "::@@\u0002\u0000CCFF\u0001\u0000XY\u0001\u0000Z\\\u0002\u0000BBOO\u0002"+ + "\u0000QQSW\u0002\u0000\u0016\u0016\u0018\u0019\u0321\u0000\u00a4\u0001"+ + "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ + "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ + "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ + "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ + "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ + "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ + "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ + "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ + "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ + "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ + "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ + "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ + "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ + "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ + ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ + "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ + "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0191\u0001\u0000\u0000\u0000"+ + "L\u0195\u0001\u0000\u0000\u0000N\u019b\u0001\u0000\u0000\u0000P\u019f"+ + "\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000\u0000\u0000T\u01aa\u0001\u0000"+ + "\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000X\u01b1\u0001\u0000\u0000\u0000"+ + "Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8\u0001\u0000\u0000\u0000^\u01cc"+ + "\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000\u0000\u0000b\u01d5\u0001\u0000"+ + "\u0000\u0000d\u01db\u0001\u0000\u0000\u0000f\u01e8\u0001\u0000\u0000\u0000"+ + "h\u01eb\u0001\u0000\u0000\u0000j\u01ef\u0001\u0000\u0000\u0000l\u01f3"+ + "\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000\u0000\u0000p\u0208\u0001\u0000"+ + "\u0000\u0000r\u020a\u0001\u0000\u0000\u0000t\u020c\u0001\u0000\u0000\u0000"+ + "v\u0214\u0001\u0000\u0000\u0000x\u021c\u0001\u0000\u0000\u0000z\u023c"+ + "\u0001\u0000\u0000\u0000|\u0257\u0001\u0000\u0000\u0000~\u0259\u0001\u0000"+ + "\u0000\u0000\u0080\u0266\u0001\u0000\u0000\u0000\u0082\u026c\u0001\u0000"+ + "\u0000\u0000\u0084\u0281\u0001\u0000\u0000\u0000\u0086\u028b\u0001\u0000"+ + "\u0000\u0000\u0088\u029e\u0001\u0000\u0000\u0000\u008a\u02a0\u0001\u0000"+ + "\u0000\u0000\u008c\u02ab\u0001\u0000\u0000\u0000\u008e\u02d9\u0001\u0000"+ + "\u0000\u0000\u0090\u02db\u0001\u0000\u0000\u0000\u0092\u02df\u0001\u0000"+ + "\u0000\u0000\u0094\u02e2\u0001\u0000\u0000\u0000\u0096\u02e7\u0001\u0000"+ + "\u0000\u0000\u0098\u02eb\u0001\u0000\u0000\u0000\u009a\u02ed\u0001\u0000"+ + "\u0000\u0000\u009c\u02ef\u0001\u0000\u0000\u0000\u009e\u02f4\u0001\u0000"+ + "\u0000\u0000\u00a0\u02f6\u0001\u0000\u0000\u0000\u00a2\u02ff\u0001\u0000"+ + "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ + "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ + "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ + "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ + "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ + "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ + "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ + "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ + "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ + "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ + "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ + "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ + "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ + "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ + "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ + "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ + "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ + "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ + "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ + "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ + "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ + "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ + "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ + "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ + "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ + "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ + "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ + "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ + "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005?\u0000"+ + "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ + "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ + "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ + "\u00eb\u0005;\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ + "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ + "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ + "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005?\u0000"+ + "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ + "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ + "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ + "\u00fa\u0005;\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ + "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ + "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ + "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ + "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ + "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005?\u0000\u0000"+ + "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ + "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ + "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ + "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ + "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ + "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ + "\u0110\u0005>\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ + "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ + "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ + "\"\u0011\u0000\u0115\u0116\u0005=\u0000\u0000\u0116\u0118\u0003 \u0010"+ + "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ + "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ + "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ + "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ + "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ + "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ + "\u0122\u0005k\u0000\u0000\u0122\u0127\u0005l\u0000\u0000\u0123\u0124\u0005"+ + "?\u0000\u0000\u0124\u0126\u0005l\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ + "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ + "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ + "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ + "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ + "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ + "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ + "\u0000\u0000\u0000\u0131\u0132\u0005<\u0000\u0000\u0132\u0134\u0003\u000e"+ + "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ + "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ + "\u0136\u0137\u0005?\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ + "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ + "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ + "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ + "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ + "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ + "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ + "\u0144\u0005A\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ + "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ + "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ + "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ + "\u0000\u014b\u014c\u0005A\u0000\u0000\u014c\u014e\u00036\u001b\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"+ + "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ + "\u00030\u0018\u0000\u0153\u0154\u0005?\u0000\u0000\u0154\u0156\u00030"+ + "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ + "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ + "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ + "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ + "\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ + "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ + "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ + "\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000\u0162\u0164\u0005`\u0000"+ + "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ + "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005_\u0000\u0000\u0166"+ + "\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ + "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ + "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ + "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ + "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ + "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ + "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ + "\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174\u0176\u0003B!\u0000"+ + "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ + "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ + "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ + "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ + "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ + "\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f\u0181\u0007\u0003"+ + "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ + "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ + "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ + "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ + "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ + "\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ + "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ + "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ + "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ + "\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194\u00030"+ + "\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b\u0000"+ + "\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000\u0198"+ + "\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a\u0001"+ + "\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005\n"+ + "\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098L\u0000"+ + "\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000\u01a0"+ + "\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7\u0003"+ + "T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ + "S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa\u01ab"+ + "\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003\u008e"+ + "G\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000\u0000"+ + "\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1\u01b2"+ + "\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4\u0005"+ + "c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!\u0000"+ + "\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000\u0000"+ + "\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000\u01ba"+ + "\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba\u0001"+ + "\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7\u0001"+ + "\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003^/"+ + "\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ + "\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000\u01c4"+ + "\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+ + "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7"+ + "\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8"+ + "]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb\u0005"+ + ";\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001\u0000"+ + "\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001\u0000"+ + "\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000\u0000"+ + "\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e\u0000"+ + "\u01d2\u01d3\u0005K\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4a"+ + "\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ + "\u0003*\u0015\u0000\u01d7\u01d8\u0005<\u0000\u0000\u01d8\u01da\u0003\u000e"+ "\u0007\u0000\u01d9\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001\u0000"+ "\u0000\u0000\u01dac\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0004\u0000"+ "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005J\u0000\u0000\u01de"+ @@ -7342,144 +7361,143 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u020f\u0005J\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ "\u0005O\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ "\u0000\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213u\u0001\u0000\u0000"+ - "\u0000\u0214\u0218\u0005\u0007\u0000\u0000\u0215\u0216\u0003.\u0017\u0000"+ - "\u0216\u0217\u0005:\u0000\u0000\u0217\u0219\u0001\u0000\u0000\u0000\u0218"+ - "\u0215\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219"+ - "\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0003\u0084B\u0000\u021b\u021c"+ - "\u0005O\u0000\u0000\u021c\u021d\u0003<\u001e\u0000\u021dw\u0001\u0000"+ - "\u0000\u0000\u021e\u021f\u0005\u0012\u0000\u0000\u021f\u0220\u0003\u0094"+ - "J\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222\u0006=\uffff\uffff"+ - "\u0000\u0222\u0223\u0005G\u0000\u0000\u0223\u023f\u0003z=\b\u0224\u023f"+ - "\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228\u0003\u0080"+ - "@\u0000\u0227\u0229\u0005G\u0000\u0000\u0228\u0227\u0001\u0000\u0000\u0000"+ - "\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000\u0000\u0000"+ - "\u022a\u022b\u0005C\u0000\u0000\u022b\u022c\u0005c\u0000\u0000\u022c\u0231"+ - "\u0003\u0080@\u0000\u022d\u022e\u0005>\u0000\u0000\u022e\u0230\u0003\u0080"+ - "@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001\u0000\u0000"+ - "\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001\u0000\u0000"+ - "\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001\u0000\u0000"+ - "\u0000\u0234\u0235\u0005d\u0000\u0000\u0235\u023f\u0001\u0000\u0000\u0000"+ - "\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005D\u0000\u0000\u0238\u023a"+ - "\u0005G\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239\u023a\u0001"+ - "\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0005"+ - "H\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f\u0003~?"+ - "\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000\u0000"+ - "\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000\u0000"+ - "\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000\u0000"+ - "\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000\u0000"+ - "\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243\u0244"+ - "\n\u0004\u0000\u0000\u0244\u0245\u0005K\u0000\u0000\u0245\u0247\u0003"+ - "z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000\u0000"+ - "\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000"+ - "\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000\u0000"+ - "\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000\u024c"+ - "\u024e\u0005G\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e"+ - "\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250"+ - "\u0005F\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001\u0000"+ - "\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005G\u0000\u0000"+ - "\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000"+ - "\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005M\u0000\u0000\u0257"+ - "\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259\u024b"+ - "\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a}\u0001"+ - "\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005<\u0000"+ - "\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000\u0000\u0000"+ - "\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000\u0000\u0000"+ - "\u0260\u0261\u0005=\u0000\u0000\u0261\u0262\u0003\u008eG\u0000\u0262\u007f"+ - "\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264\u0265\u0003"+ - "\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003\u0082A"+ - "\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000\u0000"+ - "\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000\u0000"+ - "\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084B\u0000"+ - "\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003\u026e"+ - "\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ - "\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271\u0272"+ - "\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274\n"+ - "\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ - "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ - "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ - "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ - "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ - "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ - "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005c\u0000\u0000\u0280\u0281"+ - "\u0003z=\u0000\u0281\u0282\u0005d\u0000\u0000\u0282\u0284\u0001\u0000"+ - "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ - "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ - "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ - "\u0000\u0286\u0287\u0005<\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ - "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ - "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ - "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ - "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005c\u0000\u0000\u028f\u029d"+ - "\u0005Y\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005>\u0000"+ - "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ - "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ - "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ - "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005>\u0000\u0000\u0299\u029b"+ - "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ - "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ - "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ - "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ - "d\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ - "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005\\\u0000\u0000"+ - "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005>\u0000\u0000\u02a5\u02a7"+ - "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ - "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ - "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ - "\u0000\u0000\u0000\u02ab\u02ac\u0005]\u0000\u0000\u02ac\u008b\u0001\u0000"+ - "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005=\u0000\u0000"+ - "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ - "\u02dc\u0005H\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ - "e\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ - "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ - "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ - "\u0005a\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005>"+ - "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ - "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ - "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ - "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000"+ - "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005a\u0000\u0000\u02c6"+ - "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005>\u0000\u0000\u02c8\u02ca\u0003"+ - "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ - "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ - "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ - "\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ - "\u0000\u02d0\u02d1\u0005a\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ - "\u02d3\u0005>\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ - "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ - "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ - "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ - "b\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ - "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ - "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ - "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ - "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ - "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ - "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ - "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ - "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ - "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ - "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ - "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ - "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ - "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ - "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ - "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ - "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ - "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005r"+ - "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ - "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ - "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005J\u0000\u0000\u02f9"+ - "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005>\u0000\u0000\u02fb\u02fd\u0003"+ - "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ - "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ - "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ - "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ - "\u0000F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ - "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ - "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ - "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u0218\u0228\u0231\u0239\u023e\u0246"+ - "\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a\u0295"+ - "\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; + "\u0000\u0214\u0215\u0005\u0007\u0000\u0000\u0215\u0216\u0003\u0084B\u0000"+ + "\u0216\u0217\u0005P\u0000\u0000\u0217\u021a\u0003<\u001e\u0000\u0218\u0219"+ + "\u00059\u0000\u0000\u0219\u021b\u0003.\u0017\u0000\u021a\u0218\u0001\u0000"+ + "\u0000\u0000\u021a\u021b\u0001\u0000\u0000\u0000\u021bw\u0001\u0000\u0000"+ + "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021e\u0003\u0094J\u0000"+ + "\u021ey\u0001\u0000\u0000\u0000\u021f\u0220\u0006=\uffff\uffff\u0000\u0220"+ + "\u0221\u0005H\u0000\u0000\u0221\u023d\u0003z=\b\u0222\u023d\u0003\u0080"+ + "@\u0000\u0223\u023d\u0003|>\u0000\u0224\u0226\u0003\u0080@\u0000\u0225"+ + "\u0227\u0005H\u0000\u0000\u0226\u0225\u0001\u0000\u0000\u0000\u0226\u0227"+ + "\u0001\u0000\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228\u0229"+ + "\u0005D\u0000\u0000\u0229\u022a\u0005d\u0000\u0000\u022a\u022f\u0003\u0080"+ + "@\u0000\u022b\u022c\u0005?\u0000\u0000\u022c\u022e\u0003\u0080@\u0000"+ + "\u022d\u022b\u0001\u0000\u0000\u0000\u022e\u0231\u0001\u0000\u0000\u0000"+ + "\u022f\u022d\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000"+ + "\u0230\u0232\u0001\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000"+ + "\u0232\u0233\u0005e\u0000\u0000\u0233\u023d\u0001\u0000\u0000\u0000\u0234"+ + "\u0235\u0003\u0080@\u0000\u0235\u0237\u0005E\u0000\u0000\u0236\u0238\u0005"+ + "H\u0000\u0000\u0237\u0236\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000"+ + "\u0000\u0000\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0005I\u0000"+ + "\u0000\u023a\u023d\u0001\u0000\u0000\u0000\u023b\u023d\u0003~?\u0000\u023c"+ + "\u021f\u0001\u0000\u0000\u0000\u023c\u0222\u0001\u0000\u0000\u0000\u023c"+ + "\u0223\u0001\u0000\u0000\u0000\u023c\u0224\u0001\u0000\u0000\u0000\u023c"+ + "\u0234\u0001\u0000\u0000\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d"+ + "\u0246\u0001\u0000\u0000\u0000\u023e\u023f\n\u0005\u0000\u0000\u023f\u0240"+ + "\u00058\u0000\u0000\u0240\u0245\u0003z=\u0006\u0241\u0242\n\u0004\u0000"+ + "\u0000\u0242\u0243\u0005L\u0000\u0000\u0243\u0245\u0003z=\u0005\u0244"+ + "\u023e\u0001\u0000\u0000\u0000\u0244\u0241\u0001\u0000\u0000\u0000\u0245"+ + "\u0248\u0001\u0000\u0000\u0000\u0246\u0244\u0001\u0000\u0000\u0000\u0246"+ + "\u0247\u0001\u0000\u0000\u0000\u0247{\u0001\u0000\u0000\u0000\u0248\u0246"+ + "\u0001\u0000\u0000\u0000\u0249\u024b\u0003\u0080@\u0000\u024a\u024c\u0005"+ + "H\u0000\u0000\u024b\u024a\u0001\u0000\u0000\u0000\u024b\u024c\u0001\u0000"+ + "\u0000\u0000\u024c\u024d\u0001\u0000\u0000\u0000\u024d\u024e\u0005G\u0000"+ + "\u0000\u024e\u024f\u0003\u0098L\u0000\u024f\u0258\u0001\u0000\u0000\u0000"+ + "\u0250\u0252\u0003\u0080@\u0000\u0251\u0253\u0005H\u0000\u0000\u0252\u0251"+ + "\u0001\u0000\u0000\u0000\u0252\u0253\u0001\u0000\u0000\u0000\u0253\u0254"+ + "\u0001\u0000\u0000\u0000\u0254\u0255\u0005N\u0000\u0000\u0255\u0256\u0003"+ + "\u0098L\u0000\u0256\u0258\u0001\u0000\u0000\u0000\u0257\u0249\u0001\u0000"+ + "\u0000\u0000\u0257\u0250\u0001\u0000\u0000\u0000\u0258}\u0001\u0000\u0000"+ + "\u0000\u0259\u025c\u0003.\u0017\u0000\u025a\u025b\u0005=\u0000\u0000\u025b"+ + "\u025d\u0003\n\u0005\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025c\u025d"+ + "\u0001\u0000\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f"+ + "\u0005>\u0000\u0000\u025f\u0260\u0003\u008eG\u0000\u0260\u007f\u0001\u0000"+ + "\u0000\u0000\u0261\u0267\u0003\u0082A\u0000\u0262\u0263\u0003\u0082A\u0000"+ + "\u0263\u0264\u0003\u009aM\u0000\u0264\u0265\u0003\u0082A\u0000\u0265\u0267"+ + "\u0001\u0000\u0000\u0000\u0266\u0261\u0001\u0000\u0000\u0000\u0266\u0262"+ + "\u0001\u0000\u0000\u0000\u0267\u0081\u0001\u0000\u0000\u0000\u0268\u0269"+ + "\u0006A\uffff\uffff\u0000\u0269\u026d\u0003\u0084B\u0000\u026a\u026b\u0007"+ + "\u0004\u0000\u0000\u026b\u026d\u0003\u0082A\u0003\u026c\u0268\u0001\u0000"+ + "\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026d\u0276\u0001\u0000"+ + "\u0000\u0000\u026e\u026f\n\u0002\u0000\u0000\u026f\u0270\u0007\u0005\u0000"+ + "\u0000\u0270\u0275\u0003\u0082A\u0003\u0271\u0272\n\u0001\u0000\u0000"+ + "\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0002\u0274"+ + "\u026e\u0001\u0000\u0000\u0000\u0274\u0271\u0001\u0000\u0000\u0000\u0275"+ + "\u0278\u0001\u0000\u0000\u0000\u0276\u0274\u0001\u0000\u0000\u0000\u0276"+ + "\u0277\u0001\u0000\u0000\u0000\u0277\u0083\u0001\u0000\u0000\u0000\u0278"+ + "\u0276\u0001\u0000\u0000\u0000\u0279\u027a\u0006B\uffff\uffff\u0000\u027a"+ + "\u0282\u0003\u008eG\u0000\u027b\u0282\u0003.\u0017\u0000\u027c\u0282\u0003"+ + "\u0086C\u0000\u027d\u027e\u0005d\u0000\u0000\u027e\u027f\u0003z=\u0000"+ + "\u027f\u0280\u0005e\u0000\u0000\u0280\u0282\u0001\u0000\u0000\u0000\u0281"+ + "\u0279\u0001\u0000\u0000\u0000\u0281\u027b\u0001\u0000\u0000\u0000\u0281"+ + "\u027c\u0001\u0000\u0000\u0000\u0281\u027d\u0001\u0000\u0000\u0000\u0282"+ + "\u0288\u0001\u0000\u0000\u0000\u0283\u0284\n\u0001\u0000\u0000\u0284\u0285"+ + "\u0005=\u0000\u0000\u0285\u0287\u0003\n\u0005\u0000\u0286\u0283\u0001"+ + "\u0000\u0000\u0000\u0287\u028a\u0001\u0000\u0000\u0000\u0288\u0286\u0001"+ + "\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000\u0289\u0085\u0001"+ + "\u0000\u0000\u0000\u028a\u0288\u0001\u0000\u0000\u0000\u028b\u028c\u0003"+ + "\u0088D\u0000\u028c\u029a\u0005d\u0000\u0000\u028d\u029b\u0005Z\u0000"+ + "\u0000\u028e\u0293\u0003z=\u0000\u028f\u0290\u0005?\u0000\u0000\u0290"+ + "\u0292\u0003z=\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0295\u0001"+ + "\u0000\u0000\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0293\u0294\u0001"+ + "\u0000\u0000\u0000\u0294\u0298\u0001\u0000\u0000\u0000\u0295\u0293\u0001"+ + "\u0000\u0000\u0000\u0296\u0297\u0005?\u0000\u0000\u0297\u0299\u0003\u008a"+ + "E\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0298\u0299\u0001\u0000\u0000"+ + "\u0000\u0299\u029b\u0001\u0000\u0000\u0000\u029a\u028d\u0001\u0000\u0000"+ + "\u0000\u029a\u028e\u0001\u0000\u0000\u0000\u029a\u029b\u0001\u0000\u0000"+ + "\u0000\u029b\u029c\u0001\u0000\u0000\u0000\u029c\u029d\u0005e\u0000\u0000"+ + "\u029d\u0087\u0001\u0000\u0000\u0000\u029e\u029f\u0003<\u001e\u0000\u029f"+ + "\u0089\u0001\u0000\u0000\u0000\u02a0\u02a1\u0005]\u0000\u0000\u02a1\u02a6"+ + "\u0003\u008cF\u0000\u02a2\u02a3\u0005?\u0000\u0000\u02a3\u02a5\u0003\u008c"+ + "F\u0000\u02a4\u02a2\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\u02aa\u0005^\u0000\u0000\u02aa\u008b\u0001\u0000\u0000\u0000"+ + "\u02ab\u02ac\u0003\u0098L\u0000\u02ac\u02ad\u0005>\u0000\u0000\u02ad\u02ae"+ + "\u0003\u008eG\u0000\u02ae\u008d\u0001\u0000\u0000\u0000\u02af\u02da\u0005"+ + "I\u0000\u0000\u02b0\u02b1\u0003\u0096K\u0000\u02b1\u02b2\u0005f\u0000"+ + "\u0000\u02b2\u02da\u0001\u0000\u0000\u0000\u02b3\u02da\u0003\u0094J\u0000"+ + "\u02b4\u02da\u0003\u0096K\u0000\u02b5\u02da\u0003\u0090H\u0000\u02b6\u02da"+ + "\u00038\u001c\u0000\u02b7\u02da\u0003\u0098L\u0000\u02b8\u02b9\u0005b"+ + "\u0000\u0000\u02b9\u02be\u0003\u0092I\u0000\u02ba\u02bb\u0005?\u0000\u0000"+ + "\u02bb\u02bd\u0003\u0092I\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd"+ + "\u02c0\u0001\u0000\u0000\u0000\u02be\u02bc\u0001\u0000\u0000\u0000\u02be"+ + "\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c1\u0001\u0000\u0000\u0000\u02c0"+ + "\u02be\u0001\u0000\u0000\u0000\u02c1\u02c2\u0005c\u0000\u0000\u02c2\u02da"+ + "\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000\u02c4\u02c9\u0003"+ + "\u0090H\u0000\u02c5\u02c6\u0005?\u0000\u0000\u02c6\u02c8\u0003\u0090H"+ + "\u0000\u02c7\u02c5\u0001\u0000\u0000\u0000\u02c8\u02cb\u0001\u0000\u0000"+ + "\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000"+ + "\u0000\u02ca\u02cc\u0001\u0000\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000"+ + "\u0000\u02cc\u02cd\u0005c\u0000\u0000\u02cd\u02da\u0001\u0000\u0000\u0000"+ + "\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02d4\u0003\u0098L\u0000\u02d0\u02d1"+ + "\u0005?\u0000\u0000\u02d1\u02d3\u0003\u0098L\u0000\u02d2\u02d0\u0001\u0000"+ + "\u0000\u0000\u02d3\u02d6\u0001\u0000\u0000\u0000\u02d4\u02d2\u0001\u0000"+ + "\u0000\u0000\u02d4\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d7\u0001\u0000"+ + "\u0000\u0000\u02d6\u02d4\u0001\u0000\u0000\u0000\u02d7\u02d8\u0005c\u0000"+ + "\u0000\u02d8\u02da\u0001\u0000\u0000\u0000\u02d9\u02af\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b0\u0001\u0000\u0000\u0000\u02d9\u02b3\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b4\u0001\u0000\u0000\u0000\u02d9\u02b5\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b6\u0001\u0000\u0000\u0000\u02d9\u02b7\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b8\u0001\u0000\u0000\u0000\u02d9\u02c3\u0001\u0000\u0000"+ + "\u0000\u02d9\u02ce\u0001\u0000\u0000\u0000\u02da\u008f\u0001\u0000\u0000"+ + "\u0000\u02db\u02dc\u0007\u0006\u0000\u0000\u02dc\u0091\u0001\u0000\u0000"+ + "\u0000\u02dd\u02e0\u0003\u0094J\u0000\u02de\u02e0\u0003\u0096K\u0000\u02df"+ + "\u02dd\u0001\u0000\u0000\u0000\u02df\u02de\u0001\u0000\u0000\u0000\u02e0"+ + "\u0093\u0001\u0000\u0000\u0000\u02e1\u02e3\u0007\u0004\u0000\u0000\u02e2"+ + "\u02e1\u0001\u0000\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ + "\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u00057\u0000\u0000\u02e5\u0095"+ + "\u0001\u0000\u0000\u0000\u02e6\u02e8\u0007\u0004\u0000\u0000\u02e7\u02e6"+ + "\u0001\u0000\u0000\u0000\u02e7\u02e8\u0001\u0000\u0000\u0000\u02e8\u02e9"+ + "\u0001\u0000\u0000\u0000\u02e9\u02ea\u00056\u0000\u0000\u02ea\u0097\u0001"+ + "\u0000\u0000\u0000\u02eb\u02ec\u00055\u0000\u0000\u02ec\u0099\u0001\u0000"+ + "\u0000\u0000\u02ed\u02ee\u0007\u0007\u0000\u0000\u02ee\u009b\u0001\u0000"+ + "\u0000\u0000\u02ef\u02f0\u0007\b\u0000\u0000\u02f0\u02f1\u0005s\u0000"+ + "\u0000\u02f1\u02f2\u0003\u009eO\u0000\u02f2\u02f3\u0003\u00a0P\u0000\u02f3"+ + "\u009d\u0001\u0000\u0000\u0000\u02f4\u02f5\u0003\u001c\u000e\u0000\u02f5"+ + "\u009f\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005K\u0000\u0000\u02f7\u02fc"+ + "\u0003\u00a2Q\u0000\u02f8\u02f9\u0005?\u0000\u0000\u02f9\u02fb\u0003\u00a2"+ + "Q\u0000\u02fa\u02f8\u0001\u0000\u0000\u0000\u02fb\u02fe\u0001\u0000\u0000"+ + "\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fc\u02fd\u0001\u0000\u0000"+ + "\u0000\u02fd\u00a1\u0001\u0000\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000"+ + "\u0000\u02ff\u0300\u0003\u0080@\u0000\u0300\u00a3\u0001\u0000\u0000\u0000"+ + "F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117\u0119"+ + "\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167\u016c"+ + "\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9\u01df"+ + "\u01e6\u01f1\u01ff\u0208\u0212\u021a\u0226\u022f\u0237\u023c\u0244\u0246"+ + "\u024b\u0252\u0257\u025c\u0266\u026c\u0274\u0276\u0281\u0288\u0293\u0298"+ + "\u029a\u02a6\u02be\u02c9\u02d4\u02d9\u02df\u02e2\u02e7\u02fc"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { From 3b1c5d6078f204e066e04839358c24e07bc856ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 10 Jun 2025 17:54:48 +0200 Subject: [PATCH 041/102] ES|QL Completion command syntax change (#129189) --- .../xpack/esql/parser/EsqlBaseParser.interp | 2 +- .../xpack/esql/parser/EsqlBaseParser.java | 1065 +++++++++-------- 2 files changed, 534 insertions(+), 533 deletions(-) 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 02f18d8fa1726..7638709eb9577 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 @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 770, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 551, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 558, 8, 61, 10, 61, 12, 61, 561, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 568, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 573, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 581, 8, 61, 10, 61, 12, 61, 584, 9, 61, 1, 62, 1, 62, 3, 62, 588, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 595, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 600, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 605, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 615, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 621, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 629, 8, 65, 10, 65, 12, 65, 632, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 642, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 647, 8, 66, 10, 66, 12, 66, 650, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 658, 8, 67, 10, 67, 12, 67, 661, 9, 67, 1, 67, 1, 67, 3, 67, 665, 8, 67, 3, 67, 667, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 677, 8, 69, 10, 69, 12, 69, 680, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 701, 8, 71, 10, 71, 12, 71, 704, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 712, 8, 71, 10, 71, 12, 71, 715, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 723, 8, 71, 10, 71, 12, 71, 726, 9, 71, 1, 71, 1, 71, 3, 71, 730, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 736, 8, 73, 1, 74, 3, 74, 739, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 744, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 763, 8, 80, 10, 80, 12, 80, 766, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 801, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 572, 1, 0, 0, 0, 124, 599, 1, 0, 0, 0, 126, 601, 1, 0, 0, 0, 128, 614, 1, 0, 0, 0, 130, 620, 1, 0, 0, 0, 132, 641, 1, 0, 0, 0, 134, 651, 1, 0, 0, 0, 136, 670, 1, 0, 0, 0, 138, 672, 1, 0, 0, 0, 140, 683, 1, 0, 0, 0, 142, 729, 1, 0, 0, 0, 144, 731, 1, 0, 0, 0, 146, 735, 1, 0, 0, 0, 148, 738, 1, 0, 0, 0, 150, 743, 1, 0, 0, 0, 152, 747, 1, 0, 0, 0, 154, 749, 1, 0, 0, 0, 156, 751, 1, 0, 0, 0, 158, 756, 1, 0, 0, 0, 160, 758, 1, 0, 0, 0, 162, 767, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 542, 3, 148, 74, 0, 542, 121, 1, 0, 0, 0, 543, 544, 6, 61, -1, 0, 544, 545, 5, 72, 0, 0, 545, 573, 3, 122, 61, 8, 546, 573, 3, 128, 64, 0, 547, 573, 3, 124, 62, 0, 548, 550, 3, 128, 64, 0, 549, 551, 5, 72, 0, 0, 550, 549, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 5, 68, 0, 0, 553, 554, 5, 100, 0, 0, 554, 559, 3, 128, 64, 0, 555, 556, 5, 63, 0, 0, 556, 558, 3, 128, 64, 0, 557, 555, 1, 0, 0, 0, 558, 561, 1, 0, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 562, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 562, 563, 5, 101, 0, 0, 563, 573, 1, 0, 0, 0, 564, 565, 3, 128, 64, 0, 565, 567, 5, 69, 0, 0, 566, 568, 5, 72, 0, 0, 567, 566, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 5, 73, 0, 0, 570, 573, 1, 0, 0, 0, 571, 573, 3, 126, 63, 0, 572, 543, 1, 0, 0, 0, 572, 546, 1, 0, 0, 0, 572, 547, 1, 0, 0, 0, 572, 548, 1, 0, 0, 0, 572, 564, 1, 0, 0, 0, 572, 571, 1, 0, 0, 0, 573, 582, 1, 0, 0, 0, 574, 575, 10, 5, 0, 0, 575, 576, 5, 56, 0, 0, 576, 581, 3, 122, 61, 6, 577, 578, 10, 4, 0, 0, 578, 579, 5, 76, 0, 0, 579, 581, 3, 122, 61, 5, 580, 574, 1, 0, 0, 0, 580, 577, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 123, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 3, 128, 64, 0, 586, 588, 5, 72, 0, 0, 587, 586, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 5, 71, 0, 0, 590, 591, 3, 152, 76, 0, 591, 600, 1, 0, 0, 0, 592, 594, 3, 128, 64, 0, 593, 595, 5, 72, 0, 0, 594, 593, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 5, 78, 0, 0, 597, 598, 3, 152, 76, 0, 598, 600, 1, 0, 0, 0, 599, 585, 1, 0, 0, 0, 599, 592, 1, 0, 0, 0, 600, 125, 1, 0, 0, 0, 601, 604, 3, 46, 23, 0, 602, 603, 5, 61, 0, 0, 603, 605, 3, 10, 5, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 5, 62, 0, 0, 607, 608, 3, 142, 71, 0, 608, 127, 1, 0, 0, 0, 609, 615, 3, 130, 65, 0, 610, 611, 3, 130, 65, 0, 611, 612, 3, 154, 77, 0, 612, 613, 3, 130, 65, 0, 613, 615, 1, 0, 0, 0, 614, 609, 1, 0, 0, 0, 614, 610, 1, 0, 0, 0, 615, 129, 1, 0, 0, 0, 616, 617, 6, 65, -1, 0, 617, 621, 3, 132, 66, 0, 618, 619, 7, 4, 0, 0, 619, 621, 3, 130, 65, 3, 620, 616, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 630, 1, 0, 0, 0, 622, 623, 10, 2, 0, 0, 623, 624, 7, 5, 0, 0, 624, 629, 3, 130, 65, 3, 625, 626, 10, 1, 0, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 2, 628, 622, 1, 0, 0, 0, 628, 625, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 131, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 6, 66, -1, 0, 634, 642, 3, 142, 71, 0, 635, 642, 3, 46, 23, 0, 636, 642, 3, 134, 67, 0, 637, 638, 5, 100, 0, 0, 638, 639, 3, 122, 61, 0, 639, 640, 5, 101, 0, 0, 640, 642, 1, 0, 0, 0, 641, 633, 1, 0, 0, 0, 641, 635, 1, 0, 0, 0, 641, 636, 1, 0, 0, 0, 641, 637, 1, 0, 0, 0, 642, 648, 1, 0, 0, 0, 643, 644, 10, 1, 0, 0, 644, 645, 5, 61, 0, 0, 645, 647, 3, 10, 5, 0, 646, 643, 1, 0, 0, 0, 647, 650, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 133, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 651, 652, 3, 136, 68, 0, 652, 666, 5, 100, 0, 0, 653, 667, 5, 90, 0, 0, 654, 659, 3, 122, 61, 0, 655, 656, 5, 63, 0, 0, 656, 658, 3, 122, 61, 0, 657, 655, 1, 0, 0, 0, 658, 661, 1, 0, 0, 0, 659, 657, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 664, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 662, 663, 5, 63, 0, 0, 663, 665, 3, 138, 69, 0, 664, 662, 1, 0, 0, 0, 664, 665, 1, 0, 0, 0, 665, 667, 1, 0, 0, 0, 666, 653, 1, 0, 0, 0, 666, 654, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 669, 5, 101, 0, 0, 669, 135, 1, 0, 0, 0, 670, 671, 3, 60, 30, 0, 671, 137, 1, 0, 0, 0, 672, 673, 5, 93, 0, 0, 673, 678, 3, 140, 70, 0, 674, 675, 5, 63, 0, 0, 675, 677, 3, 140, 70, 0, 676, 674, 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, 682, 5, 94, 0, 0, 682, 139, 1, 0, 0, 0, 683, 684, 3, 152, 76, 0, 684, 685, 5, 62, 0, 0, 685, 686, 3, 142, 71, 0, 686, 141, 1, 0, 0, 0, 687, 730, 5, 73, 0, 0, 688, 689, 3, 150, 75, 0, 689, 690, 5, 102, 0, 0, 690, 730, 1, 0, 0, 0, 691, 730, 3, 148, 74, 0, 692, 730, 3, 150, 75, 0, 693, 730, 3, 144, 72, 0, 694, 730, 3, 56, 28, 0, 695, 730, 3, 152, 76, 0, 696, 697, 5, 98, 0, 0, 697, 702, 3, 146, 73, 0, 698, 699, 5, 63, 0, 0, 699, 701, 3, 146, 73, 0, 700, 698, 1, 0, 0, 0, 701, 704, 1, 0, 0, 0, 702, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 705, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 730, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 713, 3, 144, 72, 0, 709, 710, 5, 63, 0, 0, 710, 712, 3, 144, 72, 0, 711, 709, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 99, 0, 0, 717, 730, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 724, 3, 152, 76, 0, 720, 721, 5, 63, 0, 0, 721, 723, 3, 152, 76, 0, 722, 720, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 727, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 728, 5, 99, 0, 0, 728, 730, 1, 0, 0, 0, 729, 687, 1, 0, 0, 0, 729, 688, 1, 0, 0, 0, 729, 691, 1, 0, 0, 0, 729, 692, 1, 0, 0, 0, 729, 693, 1, 0, 0, 0, 729, 694, 1, 0, 0, 0, 729, 695, 1, 0, 0, 0, 729, 696, 1, 0, 0, 0, 729, 707, 1, 0, 0, 0, 729, 718, 1, 0, 0, 0, 730, 143, 1, 0, 0, 0, 731, 732, 7, 6, 0, 0, 732, 145, 1, 0, 0, 0, 733, 736, 3, 148, 74, 0, 734, 736, 3, 150, 75, 0, 735, 733, 1, 0, 0, 0, 735, 734, 1, 0, 0, 0, 736, 147, 1, 0, 0, 0, 737, 739, 7, 4, 0, 0, 738, 737, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 5, 55, 0, 0, 741, 149, 1, 0, 0, 0, 742, 744, 7, 4, 0, 0, 743, 742, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 5, 54, 0, 0, 746, 151, 1, 0, 0, 0, 747, 748, 5, 53, 0, 0, 748, 153, 1, 0, 0, 0, 749, 750, 7, 7, 0, 0, 750, 155, 1, 0, 0, 0, 751, 752, 7, 8, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 3, 158, 79, 0, 754, 755, 3, 160, 80, 0, 755, 157, 1, 0, 0, 0, 756, 757, 3, 28, 14, 0, 757, 159, 1, 0, 0, 0, 758, 759, 5, 75, 0, 0, 759, 764, 3, 162, 81, 0, 760, 761, 5, 63, 0, 0, 761, 763, 3, 162, 81, 0, 762, 760, 1, 0, 0, 0, 763, 766, 1, 0, 0, 0, 764, 762, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 161, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 768, 3, 128, 64, 0, 768, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 550, 559, 567, 572, 580, 582, 587, 594, 599, 604, 614, 620, 628, 630, 641, 648, 659, 664, 666, 678, 702, 713, 724, 729, 735, 738, 743, 764] \ No newline at end of file +[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 537, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 803, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 542, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 62, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 58, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 98, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 74, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 79, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 62, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 58, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 74, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 59, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 74, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 132, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 62, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 99, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 100, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 74, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 79, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 536, 5, 7, 0, 0, 533, 534, 3, 46, 23, 0, 534, 535, 5, 58, 0, 0, 535, 537, 1, 0, 0, 0, 536, 533, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 3, 132, 66, 0, 539, 540, 5, 79, 0, 0, 540, 541, 3, 60, 30, 0, 541, 119, 1, 0, 0, 0, 542, 543, 5, 18, 0, 0, 543, 544, 3, 148, 74, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 71, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 71, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 67, 0, 0, 555, 556, 5, 99, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 62, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 100, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 68, 0, 0, 568, 570, 5, 71, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 72, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 75, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 71, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 70, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 71, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 77, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 60, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 61, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 99, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 100, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 60, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 99, 0, 0, 655, 669, 5, 89, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 62, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 62, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 100, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 92, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 62, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 93, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 61, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 72, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 101, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 97, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 62, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 97, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 62, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 97, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 62, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 98, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 114, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 74, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 62, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 536, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ 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 95f1b1fc3400f..88ce9296910f1 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 @@ -4505,7 +4505,7 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { { setState(542); match(DEV_SAMPLE); - setState(541); + setState(543); ((SampleCommandContext)_localctx).probability = decimalValue(); } } @@ -4721,7 +4721,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(572); + setState(574); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { @@ -4731,9 +4731,9 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(544); + setState(546); match(NOT); - setState(545); + setState(547); booleanExpression(8); } break; @@ -4742,7 +4742,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(548); valueExpression(); } break; @@ -4751,7 +4751,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(547); + setState(549); regexBooleanExpression(); } break; @@ -4760,41 +4760,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); - valueExpression(); setState(550); + valueExpression(); + setState(552); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(549); + setState(551); match(NOT); } } - setState(552); + setState(554); match(IN); - setState(553); + setState(555); match(LP); - setState(554); + setState(556); valueExpression(); - setState(559); + setState(561); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(555); + setState(557); match(COMMA); - setState(556); + setState(558); valueExpression(); } } - setState(561); + setState(563); _errHandler.sync(this); _la = _input.LA(1); } - setState(562); + setState(564); match(RP); } break; @@ -4803,21 +4803,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(564); + setState(566); valueExpression(); - setState(565); - match(IS); setState(567); + match(IS); + setState(569); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(566); + setState(568); match(NOT); } } - setState(569); + setState(571); match(NULL); } break; @@ -4826,13 +4826,13 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(571); + setState(573); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(582); + setState(584); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); @@ -4841,7 +4841,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(580); + setState(582); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { @@ -4850,11 +4850,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(574); + setState(576); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(575); + setState(577); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(576); + setState(578); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4863,18 +4863,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(577); + setState(579); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(578); + setState(580); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(579); + setState(581); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(584); + setState(586); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); @@ -4930,49 +4930,49 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(599); + setState(601); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(585); - valueExpression(); setState(587); + valueExpression(); + setState(589); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(586); + setState(588); match(NOT); } } - setState(589); + setState(591); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(590); + setState(592); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(592); - valueExpression(); setState(594); + valueExpression(); + setState(596); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(593); + setState(595); match(NOT); } } - setState(596); + setState(598); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(597); + setState(599); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5032,23 +5032,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(601); + setState(603); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(604); + setState(606); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(602); + setState(604); match(CAST_OP); - setState(603); + setState(605); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(606); + setState(608); match(COLON); - setState(607); + setState(609); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5132,7 +5132,7 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(614); + setState(616); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { @@ -5140,7 +5140,7 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(609); + setState(611); operatorExpression(0); } break; @@ -5148,11 +5148,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(610); + setState(612); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(611); + setState(613); comparisonOperator(); - setState(612); + setState(614); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5277,7 +5277,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(620); + setState(622); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { @@ -5287,7 +5287,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _ctx = _localctx; _prevctx = _localctx; - setState(617); + setState(619); primaryExpression(0); } break; @@ -5296,7 +5296,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(618); + setState(620); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5307,13 +5307,13 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(619); + setState(621); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(630); + setState(632); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); @@ -5322,7 +5322,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(628); + setState(630); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { @@ -5331,9 +5331,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(622); + setState(624); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(623); + setState(625); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 89)) & ~0x3f) == 0 && ((1L << (_la - 89)) & 7L) != 0)) ) { @@ -5344,7 +5344,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(624); + setState(626); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5353,9 +5353,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(625); + setState(627); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(626); + setState(628); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5366,14 +5366,14 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(627); + setState(629); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(632); + setState(634); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); @@ -5532,7 +5532,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(641); + setState(643); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { @@ -5542,7 +5542,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(634); + setState(636); constant(); } break; @@ -5551,7 +5551,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(635); + setState(637); qualifiedName(); } break; @@ -5560,7 +5560,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(638); functionExpression(); } break; @@ -5569,17 +5569,17 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(639); match(LP); - setState(638); + setState(640); booleanExpression(0); - setState(639); + setState(641); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(648); + setState(650); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); @@ -5591,16 +5591,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(643); + setState(645); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(644); + setState(646); match(CAST_OP); - setState(645); + setState(647); dataType(); } } } - setState(650); + setState(652); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); @@ -5667,16 +5667,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(651); + setState(653); functionName(); - setState(652); + setState(654); match(LP); - setState(666); + setState(668); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(653); + setState(655); match(ASTERISK); } break; @@ -5699,9 +5699,9 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(654); + setState(656); booleanExpression(0); - setState(659); + setState(661); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); @@ -5709,26 +5709,26 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx if ( _alt==1 ) { { { - setState(655); + setState(657); match(COMMA); - setState(656); + setState(658); booleanExpression(0); } } } - setState(661); + setState(663); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } - setState(664); + setState(666); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(662); + setState(664); match(COMMA); - setState(663); + setState(665); mapExpression(); } } @@ -5741,7 +5741,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(668); + setState(670); match(RP); } } @@ -5787,7 +5787,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(670); + setState(672); identifierOrParameter(); } } @@ -5843,27 +5843,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(674); match(LEFT_BRACES); - setState(673); + setState(675); entryExpression(); - setState(678); + setState(680); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(674); + setState(676); match(COMMA); - setState(675); + setState(677); entryExpression(); } } - setState(680); + setState(682); _errHandler.sync(this); _la = _input.LA(1); } - setState(681); + setState(683); match(RIGHT_BRACES); } } @@ -5915,11 +5915,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(683); + setState(685); ((EntryExpressionContext)_localctx).key = string(); - setState(684); + setState(686); match(COLON); - setState(685); + setState(687); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6190,7 +6190,7 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(729); + setState(731); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { @@ -6198,7 +6198,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(687); + setState(689); match(NULL); } break; @@ -6206,9 +6206,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(688); + setState(690); integerValue(); - setState(689); + setState(691); match(UNQUOTED_IDENTIFIER); } break; @@ -6216,7 +6216,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(691); + setState(693); decimalValue(); } break; @@ -6224,7 +6224,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(692); + setState(694); integerValue(); } break; @@ -6232,7 +6232,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(693); + setState(695); booleanValue(); } break; @@ -6240,7 +6240,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(694); + setState(696); parameter(); } break; @@ -6248,7 +6248,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(695); + setState(697); string(); } break; @@ -6256,27 +6256,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(696); + setState(698); match(OPENING_BRACKET); - setState(697); + setState(699); numericValue(); - setState(702); + setState(704); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(698); + setState(700); match(COMMA); - setState(699); + setState(701); numericValue(); } } - setState(704); + setState(706); _errHandler.sync(this); _la = _input.LA(1); } - setState(705); + setState(707); match(CLOSING_BRACKET); } break; @@ -6284,27 +6284,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(707); + setState(709); match(OPENING_BRACKET); - setState(708); + setState(710); booleanValue(); - setState(713); + setState(715); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(709); + setState(711); match(COMMA); - setState(710); + setState(712); booleanValue(); } } - setState(715); + setState(717); _errHandler.sync(this); _la = _input.LA(1); } - setState(716); + setState(718); match(CLOSING_BRACKET); } break; @@ -6312,27 +6312,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(718); + setState(720); match(OPENING_BRACKET); - setState(719); + setState(721); string(); - setState(724); + setState(726); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(720); + setState(722); match(COMMA); - setState(721); + setState(723); string(); } } - setState(726); + setState(728); _errHandler.sync(this); _la = _input.LA(1); } - setState(727); + setState(729); match(CLOSING_BRACKET); } break; @@ -6380,7 +6380,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(731); + setState(733); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6435,21 +6435,21 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(735); + setState(737); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(733); + setState(735); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(734); + setState(736); integerValue(); } break; @@ -6498,12 +6498,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(738); + setState(740); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(737); + setState(739); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6516,7 +6516,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(740); + setState(742); match(DECIMAL_LITERAL); } } @@ -6563,12 +6563,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(743); + setState(745); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(742); + setState(744); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6581,7 +6581,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(745); + setState(747); match(INTEGER_LITERAL); } } @@ -6625,7 +6625,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(747); + setState(749); match(QUOTED_STRING); } } @@ -6675,7 +6675,7 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(751); _la = _input.LA(1); if ( !(((((_la - 80)) & ~0x3f) == 0 && ((1L << (_la - 80)) & 125L) != 0)) ) { _errHandler.recoverInline(this); @@ -6738,7 +6738,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(753); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6749,11 +6749,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(752); + setState(754); match(JOIN); - setState(753); + setState(755); joinTarget(); - setState(754); + setState(756); joinCondition(); } } @@ -6800,7 +6800,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(756); + setState(758); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6855,11 +6855,11 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(758); + setState(760); match(ON); - setState(759); + setState(761); joinPredicate(); - setState(764); + setState(766); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); @@ -6867,14 +6867,14 @@ public final JoinConditionContext joinCondition() throws RecognitionException { if ( _alt==1 ) { { { - setState(760); + setState(762); match(COMMA); - setState(761); + setState(763); joinPredicate(); } } } - setState(766); + setState(768); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); @@ -6923,7 +6923,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(767); + setState(769); valueExpression(); } } @@ -7024,7 +7024,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0302\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u0304\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"+ @@ -7092,243 +7092,243 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "5\u01f0\b5\u000b5\f5\u01f1\u00016\u00016\u00016\u00016\u00017\u00017\u0001"+ "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ - ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0001"+ - ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0003=\u0227\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ - "=\u0005=\u022e\b=\n=\f=\u0231\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003"+ - "=\u0238\b=\u0001=\u0001=\u0001=\u0003=\u023d\b=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0245\b=\n=\f=\u0248\t=\u0001>\u0001>\u0003>\u024c"+ - "\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0253\b>\u0001>\u0001>\u0001"+ - ">\u0003>\u0258\b>\u0001?\u0001?\u0001?\u0003?\u025d\b?\u0001?\u0001?\u0001"+ - "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0267\b@\u0001A\u0001A\u0001"+ - "A\u0001A\u0003A\u026d\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ - "A\u0275\bA\nA\fA\u0278\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0003B\u0282\bB\u0001B\u0001B\u0001B\u0005B\u0287\bB\nB\fB\u028a"+ - "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0292\bC\nC\fC\u0295"+ - "\tC\u0001C\u0001C\u0003C\u0299\bC\u0003C\u029b\bC\u0001C\u0001C\u0001"+ - "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a5\bE\nE\fE\u02a8\tE\u0001"+ - "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02bd"+ - "\bG\nG\fG\u02c0\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c8"+ - "\bG\nG\fG\u02cb\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d3"+ - "\bG\nG\fG\u02d6\tG\u0001G\u0001G\u0003G\u02da\bG\u0001H\u0001H\u0001I"+ - "\u0001I\u0003I\u02e0\bI\u0001J\u0003J\u02e3\bJ\u0001J\u0001J\u0001K\u0003"+ - "K\u02e8\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ - "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u02fb"+ - "\bP\nP\fP\u02fe\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ - "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ - "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ - "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ - "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055ll\u0001\u0000fg\u0002\u0000"+ - "::@@\u0002\u0000CCFF\u0001\u0000XY\u0001\u0000Z\\\u0002\u0000BBOO\u0002"+ - "\u0000QQSW\u0002\u0000\u0016\u0016\u0018\u0019\u0321\u0000\u00a4\u0001"+ - "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ - "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ - "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ - "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ - "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ - "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ - "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ - "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ - "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ - "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ - "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ - "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ - "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ - "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ - ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ - "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ - "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0191\u0001\u0000\u0000\u0000"+ - "L\u0195\u0001\u0000\u0000\u0000N\u019b\u0001\u0000\u0000\u0000P\u019f"+ - "\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000\u0000\u0000T\u01aa\u0001\u0000"+ - "\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000X\u01b1\u0001\u0000\u0000\u0000"+ - "Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8\u0001\u0000\u0000\u0000^\u01cc"+ - "\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000\u0000\u0000b\u01d5\u0001\u0000"+ - "\u0000\u0000d\u01db\u0001\u0000\u0000\u0000f\u01e8\u0001\u0000\u0000\u0000"+ - "h\u01eb\u0001\u0000\u0000\u0000j\u01ef\u0001\u0000\u0000\u0000l\u01f3"+ - "\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000\u0000\u0000p\u0208\u0001\u0000"+ - "\u0000\u0000r\u020a\u0001\u0000\u0000\u0000t\u020c\u0001\u0000\u0000\u0000"+ - "v\u0214\u0001\u0000\u0000\u0000x\u021c\u0001\u0000\u0000\u0000z\u023c"+ - "\u0001\u0000\u0000\u0000|\u0257\u0001\u0000\u0000\u0000~\u0259\u0001\u0000"+ - "\u0000\u0000\u0080\u0266\u0001\u0000\u0000\u0000\u0082\u026c\u0001\u0000"+ - "\u0000\u0000\u0084\u0281\u0001\u0000\u0000\u0000\u0086\u028b\u0001\u0000"+ - "\u0000\u0000\u0088\u029e\u0001\u0000\u0000\u0000\u008a\u02a0\u0001\u0000"+ - "\u0000\u0000\u008c\u02ab\u0001\u0000\u0000\u0000\u008e\u02d9\u0001\u0000"+ - "\u0000\u0000\u0090\u02db\u0001\u0000\u0000\u0000\u0092\u02df\u0001\u0000"+ - "\u0000\u0000\u0094\u02e2\u0001\u0000\u0000\u0000\u0096\u02e7\u0001\u0000"+ - "\u0000\u0000\u0098\u02eb\u0001\u0000\u0000\u0000\u009a\u02ed\u0001\u0000"+ - "\u0000\u0000\u009c\u02ef\u0001\u0000\u0000\u0000\u009e\u02f4\u0001\u0000"+ - "\u0000\u0000\u00a0\u02f6\u0001\u0000\u0000\u0000\u00a2\u02ff\u0001\u0000"+ - "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ - "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ - "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ - "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ - "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ - "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ - "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ - "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ - "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ - "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ - "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ - "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ - "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ - "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ - "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ - "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ - "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ - "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ - "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ - "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ - "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ - "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ - "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ - "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ - "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ - "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ - "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ - "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ - "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ - "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ - "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ - "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005?\u0000"+ - "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ - "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ - "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ - "\u00eb\u0005;\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ - "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ - "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ - "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005?\u0000"+ - "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ - "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ - "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ - "\u00fa\u0005;\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ - "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ - "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ - "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ - "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ - "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005?\u0000\u0000"+ - "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ - "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ - "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ - "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ - "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ - "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ - "\u0110\u0005>\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ - "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ - "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ - "\"\u0011\u0000\u0115\u0116\u0005=\u0000\u0000\u0116\u0118\u0003 \u0010"+ - "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ - "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ - "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ - "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ - "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ - "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ - "\u0122\u0005k\u0000\u0000\u0122\u0127\u0005l\u0000\u0000\u0123\u0124\u0005"+ - "?\u0000\u0000\u0124\u0126\u0005l\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ - "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ - "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ - "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ - "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ - "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ - "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ - "\u0000\u0000\u0000\u0131\u0132\u0005<\u0000\u0000\u0132\u0134\u0003\u000e"+ - "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ - "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ - "\u0136\u0137\u0005?\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ - "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ - "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ - "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ - "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ - "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ - "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ - "\u0144\u0005A\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ - "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ - "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ - "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ - "\u0000\u014b\u014c\u0005A\u0000\u0000\u014c\u014e\u00036\u001b\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"+ - "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ - "\u00030\u0018\u0000\u0153\u0154\u0005?\u0000\u0000\u0154\u0156\u00030"+ - "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ - "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ - "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ - "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ - "\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ - "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ - "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ - "\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000\u0162\u0164\u0005`\u0000"+ - "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ - "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005_\u0000\u0000\u0166"+ - "\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ - "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ - "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ - "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ - "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ - "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ - "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ - "\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174\u0176\u0003B!\u0000"+ - "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ - "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ - "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ - "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ - "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ - "\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f\u0181\u0007\u0003"+ - "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ - "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ - "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ - "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ - "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ - "\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ - "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ - "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ - "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ - "\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194\u00030"+ - "\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b\u0000"+ - "\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000\u0198"+ - "\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a\u0001"+ - "\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005\n"+ - "\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098L\u0000"+ - "\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000\u01a0"+ - "\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7\u0003"+ - "T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ - "S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa\u01ab"+ - "\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003\u008e"+ - "G\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000\u0000"+ - "\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1\u01b2"+ - "\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4\u0005"+ - "c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!\u0000"+ - "\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000\u0000"+ - "\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000\u01ba"+ - "\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba\u0001"+ - "\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7\u0001"+ - "\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003^/"+ - "\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ - "\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000\u01c4"+ - "\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+ - "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7"+ - "\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8"+ - "]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb\u0005"+ - ";\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001\u0000"+ - "\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001\u0000"+ - "\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000\u0000"+ - "\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e\u0000"+ - "\u01d2\u01d3\u0005K\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4a"+ - "\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ - "\u0003*\u0015\u0000\u01d7\u01d8\u0005<\u0000\u0000\u01d8\u01da\u0003\u000e"+ + ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0003"+ + ";\u0219\b;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001<\u0001<\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ + ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ + ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ + "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ + "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ + "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ + "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ + "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ + "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ + "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ + "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ + "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ + "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ + "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ + "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ + "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ + "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ + "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ + "5kk\u0001\u0000ef\u0002\u000099??\u0002\u0000BBEE\u0001\u0000WX\u0001"+ + "\u0000Y[\u0002\u0000AANN\u0002\u0000PPRV\u0002\u0000\u0016\u0016\u0018"+ + "\u0019\u0323\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ + "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ + "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ + "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ + "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ + "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ + "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ + "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ + " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ + "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ + "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ + ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ + "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ + "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ + "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ + "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ + "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ + "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ + "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ + "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ + "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ + "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ + "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ + "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ + "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ + "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ + "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021e"+ + "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ + "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ + "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ + "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ + "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ + "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ + "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ + "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ + "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ + "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ + "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ + "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ + "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ + "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ + "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ + "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ + "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ + "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ + "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ + "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ + "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ + "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ + "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ + "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ + " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ + "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ + "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ + "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ + "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ + "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ + "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ + "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ + "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ + "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ + "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ + "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ + "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ + "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ + "\u0000\u00e2\u00e3\u0005>\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ + "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ + "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ + "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005:\u0000\u0000\u00eb\u00ed"+ + "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ + "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ + "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ + "\n\u0000\u00f1\u00f2\u0005>\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ + "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ + "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ + "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005:\u0000\u0000\u00fa\u00fc"+ + "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ + "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ + "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ + "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ + "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ + "\u0104\u0105\u0005>\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ + "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ + "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ + "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ + "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ + "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ + "\u0003\u001e\u000f\u0000\u010f\u0110\u0005=\u0000\u0000\u0110\u0112\u0001"+ + "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ + "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ + "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005<\u0000"+ + "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ + "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ + "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ + "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ + "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ + "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ + "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005j\u0000\u0000\u0122\u0127\u0005"+ + "k\u0000\u0000\u0123\u0124\u0005>\u0000\u0000\u0124\u0126\u0005k\u0000"+ + "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ + "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ + "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ + "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ + "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ + "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ + "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ + ";\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ + "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ + "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005>\u0000\u0000\u0137"+ + "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ + "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ + "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ + "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ + "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ + "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ + "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005@\u0000\u0000\u0144\u0146\u0003"+ + "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ + "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ + "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ + "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005@\u0000\u0000\u014c"+ + "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ + "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005>\u0000"+ + "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ + "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ + "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ + "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ + "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160"+ + "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ + "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ + "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000"+ + "\u0162\u0164\u0005_\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ + "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ + "\u0005^\u0000\u0000\u0166\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000"+ + "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ + "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ + "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ + "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ + "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ + "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ + "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174"+ + "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ + "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ + "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ + "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ + "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ + "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f"+ + "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ + "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ + "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ + "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ + "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ + "\u018e\u0003J%\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003"+ + "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ + "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ + "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ + "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193"+ + "\u0194\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005"+ + "\b\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098"+ + "L\u0000\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000"+ + "\u0199\u019a\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b"+ + "\u019c\u0005\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e"+ + "\u0003\u0098L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b"+ + "\u0000\u0000\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000"+ + "\u01a2\u01a7\u0003T*\u0000\u01a3\u01a4\u0005>\u0000\u0000\u01a4\u01a6"+ + "\u0003T*\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\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000"+ + "\u0000\u01aa\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005:\u0000\u0000\u01ac"+ + "\u01ad\u0003\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005"+ + "\u0006\u0000\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000"+ + "\u0000\u01b1\u01b2\u0005a\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000"+ + "\u01b3\u01b4\u0005b\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6"+ + "\u0005!\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000"+ + "\u0000\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000"+ + "\u0000\u01ba\u01bb\u0005J\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc"+ + "\u01ba\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd"+ + "\u01c7\u0001\u0000\u0000\u0000\u01be\u01bf\u0005O\u0000\u0000\u01bf\u01c4"+ + "\u0003^/\u0000\u01c0\u01c1\u0005>\u0000\u0000\u01c1\u01c3\u0003^/\u0000"+ + "\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000"+ + "\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000"+ + "\u01c5\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000"+ + "\u01c7\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000"+ + "\u01c8]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb"+ + "\u0005:\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001"+ + "\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001"+ + "\u0000\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000"+ + "\u0000\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e"+ + "\u0000\u01d2\u01d3\u0005J\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4"+ + "a\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ + "\u0003*\u0015\u0000\u01d7\u01d8\u0005;\u0000\u0000\u01d8\u01da\u0003\u000e"+ "\u0007\u0000\u01d9\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001\u0000"+ "\u0000\u0000\u01dac\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0004\u0000"+ "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005J\u0000\u0000\u01de"+ @@ -7361,143 +7361,144 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u020f\u0005J\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ "\u0005O\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ "\u0000\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213u\u0001\u0000\u0000"+ - "\u0000\u0214\u0215\u0005\u0007\u0000\u0000\u0215\u0216\u0003\u0084B\u0000"+ - "\u0216\u0217\u0005P\u0000\u0000\u0217\u021a\u0003<\u001e\u0000\u0218\u0219"+ - "\u00059\u0000\u0000\u0219\u021b\u0003.\u0017\u0000\u021a\u0218\u0001\u0000"+ - "\u0000\u0000\u021a\u021b\u0001\u0000\u0000\u0000\u021bw\u0001\u0000\u0000"+ - "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021e\u0003\u0094J\u0000"+ - "\u021ey\u0001\u0000\u0000\u0000\u021f\u0220\u0006=\uffff\uffff\u0000\u0220"+ - "\u0221\u0005H\u0000\u0000\u0221\u023d\u0003z=\b\u0222\u023d\u0003\u0080"+ - "@\u0000\u0223\u023d\u0003|>\u0000\u0224\u0226\u0003\u0080@\u0000\u0225"+ - "\u0227\u0005H\u0000\u0000\u0226\u0225\u0001\u0000\u0000\u0000\u0226\u0227"+ - "\u0001\u0000\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228\u0229"+ - "\u0005D\u0000\u0000\u0229\u022a\u0005d\u0000\u0000\u022a\u022f\u0003\u0080"+ - "@\u0000\u022b\u022c\u0005?\u0000\u0000\u022c\u022e\u0003\u0080@\u0000"+ - "\u022d\u022b\u0001\u0000\u0000\u0000\u022e\u0231\u0001\u0000\u0000\u0000"+ - "\u022f\u022d\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000"+ - "\u0230\u0232\u0001\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000"+ - "\u0232\u0233\u0005e\u0000\u0000\u0233\u023d\u0001\u0000\u0000\u0000\u0234"+ - "\u0235\u0003\u0080@\u0000\u0235\u0237\u0005E\u0000\u0000\u0236\u0238\u0005"+ - "H\u0000\u0000\u0237\u0236\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000"+ - "\u0000\u0000\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0005I\u0000"+ - "\u0000\u023a\u023d\u0001\u0000\u0000\u0000\u023b\u023d\u0003~?\u0000\u023c"+ - "\u021f\u0001\u0000\u0000\u0000\u023c\u0222\u0001\u0000\u0000\u0000\u023c"+ - "\u0223\u0001\u0000\u0000\u0000\u023c\u0224\u0001\u0000\u0000\u0000\u023c"+ - "\u0234\u0001\u0000\u0000\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d"+ - "\u0246\u0001\u0000\u0000\u0000\u023e\u023f\n\u0005\u0000\u0000\u023f\u0240"+ - "\u00058\u0000\u0000\u0240\u0245\u0003z=\u0006\u0241\u0242\n\u0004\u0000"+ - "\u0000\u0242\u0243\u0005L\u0000\u0000\u0243\u0245\u0003z=\u0005\u0244"+ - "\u023e\u0001\u0000\u0000\u0000\u0244\u0241\u0001\u0000\u0000\u0000\u0245"+ - "\u0248\u0001\u0000\u0000\u0000\u0246\u0244\u0001\u0000\u0000\u0000\u0246"+ - "\u0247\u0001\u0000\u0000\u0000\u0247{\u0001\u0000\u0000\u0000\u0248\u0246"+ - "\u0001\u0000\u0000\u0000\u0249\u024b\u0003\u0080@\u0000\u024a\u024c\u0005"+ - "H\u0000\u0000\u024b\u024a\u0001\u0000\u0000\u0000\u024b\u024c\u0001\u0000"+ - "\u0000\u0000\u024c\u024d\u0001\u0000\u0000\u0000\u024d\u024e\u0005G\u0000"+ - "\u0000\u024e\u024f\u0003\u0098L\u0000\u024f\u0258\u0001\u0000\u0000\u0000"+ - "\u0250\u0252\u0003\u0080@\u0000\u0251\u0253\u0005H\u0000\u0000\u0252\u0251"+ - "\u0001\u0000\u0000\u0000\u0252\u0253\u0001\u0000\u0000\u0000\u0253\u0254"+ - "\u0001\u0000\u0000\u0000\u0254\u0255\u0005N\u0000\u0000\u0255\u0256\u0003"+ - "\u0098L\u0000\u0256\u0258\u0001\u0000\u0000\u0000\u0257\u0249\u0001\u0000"+ - "\u0000\u0000\u0257\u0250\u0001\u0000\u0000\u0000\u0258}\u0001\u0000\u0000"+ - "\u0000\u0259\u025c\u0003.\u0017\u0000\u025a\u025b\u0005=\u0000\u0000\u025b"+ - "\u025d\u0003\n\u0005\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025c\u025d"+ - "\u0001\u0000\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f"+ - "\u0005>\u0000\u0000\u025f\u0260\u0003\u008eG\u0000\u0260\u007f\u0001\u0000"+ - "\u0000\u0000\u0261\u0267\u0003\u0082A\u0000\u0262\u0263\u0003\u0082A\u0000"+ - "\u0263\u0264\u0003\u009aM\u0000\u0264\u0265\u0003\u0082A\u0000\u0265\u0267"+ - "\u0001\u0000\u0000\u0000\u0266\u0261\u0001\u0000\u0000\u0000\u0266\u0262"+ - "\u0001\u0000\u0000\u0000\u0267\u0081\u0001\u0000\u0000\u0000\u0268\u0269"+ - "\u0006A\uffff\uffff\u0000\u0269\u026d\u0003\u0084B\u0000\u026a\u026b\u0007"+ - "\u0004\u0000\u0000\u026b\u026d\u0003\u0082A\u0003\u026c\u0268\u0001\u0000"+ - "\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026d\u0276\u0001\u0000"+ - "\u0000\u0000\u026e\u026f\n\u0002\u0000\u0000\u026f\u0270\u0007\u0005\u0000"+ - "\u0000\u0270\u0275\u0003\u0082A\u0003\u0271\u0272\n\u0001\u0000\u0000"+ - "\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0002\u0274"+ - "\u026e\u0001\u0000\u0000\u0000\u0274\u0271\u0001\u0000\u0000\u0000\u0275"+ - "\u0278\u0001\u0000\u0000\u0000\u0276\u0274\u0001\u0000\u0000\u0000\u0276"+ - "\u0277\u0001\u0000\u0000\u0000\u0277\u0083\u0001\u0000\u0000\u0000\u0278"+ - "\u0276\u0001\u0000\u0000\u0000\u0279\u027a\u0006B\uffff\uffff\u0000\u027a"+ - "\u0282\u0003\u008eG\u0000\u027b\u0282\u0003.\u0017\u0000\u027c\u0282\u0003"+ - "\u0086C\u0000\u027d\u027e\u0005d\u0000\u0000\u027e\u027f\u0003z=\u0000"+ - "\u027f\u0280\u0005e\u0000\u0000\u0280\u0282\u0001\u0000\u0000\u0000\u0281"+ - "\u0279\u0001\u0000\u0000\u0000\u0281\u027b\u0001\u0000\u0000\u0000\u0281"+ - "\u027c\u0001\u0000\u0000\u0000\u0281\u027d\u0001\u0000\u0000\u0000\u0282"+ - "\u0288\u0001\u0000\u0000\u0000\u0283\u0284\n\u0001\u0000\u0000\u0284\u0285"+ - "\u0005=\u0000\u0000\u0285\u0287\u0003\n\u0005\u0000\u0286\u0283\u0001"+ - "\u0000\u0000\u0000\u0287\u028a\u0001\u0000\u0000\u0000\u0288\u0286\u0001"+ - "\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000\u0289\u0085\u0001"+ - "\u0000\u0000\u0000\u028a\u0288\u0001\u0000\u0000\u0000\u028b\u028c\u0003"+ - "\u0088D\u0000\u028c\u029a\u0005d\u0000\u0000\u028d\u029b\u0005Z\u0000"+ - "\u0000\u028e\u0293\u0003z=\u0000\u028f\u0290\u0005?\u0000\u0000\u0290"+ - "\u0292\u0003z=\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0295\u0001"+ - "\u0000\u0000\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0293\u0294\u0001"+ - "\u0000\u0000\u0000\u0294\u0298\u0001\u0000\u0000\u0000\u0295\u0293\u0001"+ - "\u0000\u0000\u0000\u0296\u0297\u0005?\u0000\u0000\u0297\u0299\u0003\u008a"+ - "E\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0298\u0299\u0001\u0000\u0000"+ - "\u0000\u0299\u029b\u0001\u0000\u0000\u0000\u029a\u028d\u0001\u0000\u0000"+ - "\u0000\u029a\u028e\u0001\u0000\u0000\u0000\u029a\u029b\u0001\u0000\u0000"+ - "\u0000\u029b\u029c\u0001\u0000\u0000\u0000\u029c\u029d\u0005e\u0000\u0000"+ - "\u029d\u0087\u0001\u0000\u0000\u0000\u029e\u029f\u0003<\u001e\u0000\u029f"+ - "\u0089\u0001\u0000\u0000\u0000\u02a0\u02a1\u0005]\u0000\u0000\u02a1\u02a6"+ - "\u0003\u008cF\u0000\u02a2\u02a3\u0005?\u0000\u0000\u02a3\u02a5\u0003\u008c"+ - "F\u0000\u02a4\u02a2\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\u02aa\u0005^\u0000\u0000\u02aa\u008b\u0001\u0000\u0000\u0000"+ - "\u02ab\u02ac\u0003\u0098L\u0000\u02ac\u02ad\u0005>\u0000\u0000\u02ad\u02ae"+ - "\u0003\u008eG\u0000\u02ae\u008d\u0001\u0000\u0000\u0000\u02af\u02da\u0005"+ - "I\u0000\u0000\u02b0\u02b1\u0003\u0096K\u0000\u02b1\u02b2\u0005f\u0000"+ - "\u0000\u02b2\u02da\u0001\u0000\u0000\u0000\u02b3\u02da\u0003\u0094J\u0000"+ - "\u02b4\u02da\u0003\u0096K\u0000\u02b5\u02da\u0003\u0090H\u0000\u02b6\u02da"+ - "\u00038\u001c\u0000\u02b7\u02da\u0003\u0098L\u0000\u02b8\u02b9\u0005b"+ - "\u0000\u0000\u02b9\u02be\u0003\u0092I\u0000\u02ba\u02bb\u0005?\u0000\u0000"+ - "\u02bb\u02bd\u0003\u0092I\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd"+ - "\u02c0\u0001\u0000\u0000\u0000\u02be\u02bc\u0001\u0000\u0000\u0000\u02be"+ - "\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c1\u0001\u0000\u0000\u0000\u02c0"+ - "\u02be\u0001\u0000\u0000\u0000\u02c1\u02c2\u0005c\u0000\u0000\u02c2\u02da"+ - "\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000\u02c4\u02c9\u0003"+ - "\u0090H\u0000\u02c5\u02c6\u0005?\u0000\u0000\u02c6\u02c8\u0003\u0090H"+ - "\u0000\u02c7\u02c5\u0001\u0000\u0000\u0000\u02c8\u02cb\u0001\u0000\u0000"+ - "\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000"+ - "\u0000\u02ca\u02cc\u0001\u0000\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000"+ - "\u0000\u02cc\u02cd\u0005c\u0000\u0000\u02cd\u02da\u0001\u0000\u0000\u0000"+ - "\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02d4\u0003\u0098L\u0000\u02d0\u02d1"+ - "\u0005?\u0000\u0000\u02d1\u02d3\u0003\u0098L\u0000\u02d2\u02d0\u0001\u0000"+ - "\u0000\u0000\u02d3\u02d6\u0001\u0000\u0000\u0000\u02d4\u02d2\u0001\u0000"+ - "\u0000\u0000\u02d4\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d7\u0001\u0000"+ - "\u0000\u0000\u02d6\u02d4\u0001\u0000\u0000\u0000\u02d7\u02d8\u0005c\u0000"+ - "\u0000\u02d8\u02da\u0001\u0000\u0000\u0000\u02d9\u02af\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b0\u0001\u0000\u0000\u0000\u02d9\u02b3\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b4\u0001\u0000\u0000\u0000\u02d9\u02b5\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b6\u0001\u0000\u0000\u0000\u02d9\u02b7\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b8\u0001\u0000\u0000\u0000\u02d9\u02c3\u0001\u0000\u0000"+ - "\u0000\u02d9\u02ce\u0001\u0000\u0000\u0000\u02da\u008f\u0001\u0000\u0000"+ - "\u0000\u02db\u02dc\u0007\u0006\u0000\u0000\u02dc\u0091\u0001\u0000\u0000"+ - "\u0000\u02dd\u02e0\u0003\u0094J\u0000\u02de\u02e0\u0003\u0096K\u0000\u02df"+ - "\u02dd\u0001\u0000\u0000\u0000\u02df\u02de\u0001\u0000\u0000\u0000\u02e0"+ - "\u0093\u0001\u0000\u0000\u0000\u02e1\u02e3\u0007\u0004\u0000\u0000\u02e2"+ - "\u02e1\u0001\u0000\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ - "\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u00057\u0000\u0000\u02e5\u0095"+ - "\u0001\u0000\u0000\u0000\u02e6\u02e8\u0007\u0004\u0000\u0000\u02e7\u02e6"+ - "\u0001\u0000\u0000\u0000\u02e7\u02e8\u0001\u0000\u0000\u0000\u02e8\u02e9"+ - "\u0001\u0000\u0000\u0000\u02e9\u02ea\u00056\u0000\u0000\u02ea\u0097\u0001"+ - "\u0000\u0000\u0000\u02eb\u02ec\u00055\u0000\u0000\u02ec\u0099\u0001\u0000"+ - "\u0000\u0000\u02ed\u02ee\u0007\u0007\u0000\u0000\u02ee\u009b\u0001\u0000"+ - "\u0000\u0000\u02ef\u02f0\u0007\b\u0000\u0000\u02f0\u02f1\u0005s\u0000"+ - "\u0000\u02f1\u02f2\u0003\u009eO\u0000\u02f2\u02f3\u0003\u00a0P\u0000\u02f3"+ - "\u009d\u0001\u0000\u0000\u0000\u02f4\u02f5\u0003\u001c\u000e\u0000\u02f5"+ - "\u009f\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005K\u0000\u0000\u02f7\u02fc"+ - "\u0003\u00a2Q\u0000\u02f8\u02f9\u0005?\u0000\u0000\u02f9\u02fb\u0003\u00a2"+ - "Q\u0000\u02fa\u02f8\u0001\u0000\u0000\u0000\u02fb\u02fe\u0001\u0000\u0000"+ - "\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fc\u02fd\u0001\u0000\u0000"+ - "\u0000\u02fd\u00a1\u0001\u0000\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000"+ - "\u0000\u02ff\u0300\u0003\u0080@\u0000\u0300\u00a3\u0001\u0000\u0000\u0000"+ - "F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117\u0119"+ - "\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167\u016c"+ - "\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9\u01df"+ - "\u01e6\u01f1\u01ff\u0208\u0212\u021a\u0226\u022f\u0237\u023c\u0244\u0246"+ - "\u024b\u0252\u0257\u025c\u0266\u026c\u0274\u0276\u0281\u0288\u0293\u0298"+ - "\u029a\u02a6\u02be\u02c9\u02d4\u02d9\u02df\u02e2\u02e7\u02fc"; + "\u0000\u0214\u0218\u0005\u0007\u0000\u0000\u0215\u0216\u0003.\u0017\u0000"+ + "\u0216\u0217\u0005:\u0000\u0000\u0217\u0219\u0001\u0000\u0000\u0000\u0218"+ + "\u0215\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219"+ + "\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0003\u0084B\u0000\u021b\u021c"+ + "\u0005O\u0000\u0000\u021c\u021d\u0003<\u001e\u0000\u021dw\u0001\u0000"+ + "\u0000\u0000\u021e\u021f\u0005\u0012\u0000\u0000\u021f\u0220\u0003\u0094"+ + "J\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222\u0006=\uffff\uffff"+ + "\u0000\u0222\u0223\u0005G\u0000\u0000\u0223\u023f\u0003z=\b\u0224\u023f"+ + "\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228\u0003\u0080"+ + "@\u0000\u0227\u0229\u0005G\u0000\u0000\u0228\u0227\u0001\u0000\u0000\u0000"+ + "\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000\u0000\u0000"+ + "\u022a\u022b\u0005C\u0000\u0000\u022b\u022c\u0005c\u0000\u0000\u022c\u0231"+ + "\u0003\u0080@\u0000\u022d\u022e\u0005>\u0000\u0000\u022e\u0230\u0003\u0080"+ + "@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001\u0000\u0000"+ + "\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001\u0000\u0000"+ + "\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001\u0000\u0000"+ + "\u0000\u0234\u0235\u0005d\u0000\u0000\u0235\u023f\u0001\u0000\u0000\u0000"+ + "\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005D\u0000\u0000\u0238\u023a"+ + "\u0005G\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239\u023a\u0001"+ + "\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0005"+ + "H\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f\u0003~?"+ + "\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000\u0000"+ + "\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000\u0000"+ + "\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000\u0000"+ + "\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000\u0000"+ + "\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243\u0244"+ + "\n\u0004\u0000\u0000\u0244\u0245\u0005K\u0000\u0000\u0245\u0247\u0003"+ + "z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000\u0000"+ + "\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000"+ + "\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000\u0000"+ + "\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000\u024c"+ + "\u024e\u0005G\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e"+ + "\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250"+ + "\u0005F\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001\u0000"+ + "\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005G\u0000\u0000"+ + "\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000"+ + "\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005M\u0000\u0000\u0257"+ + "\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259\u024b"+ + "\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a}\u0001"+ + "\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005<\u0000"+ + "\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000\u0000\u0000"+ + "\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000\u0000\u0000"+ + "\u0260\u0261\u0005=\u0000\u0000\u0261\u0262\u0003\u008eG\u0000\u0262\u007f"+ + "\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264\u0265\u0003"+ + "\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003\u0082A"+ + "\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000\u0000"+ + "\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000\u0000"+ + "\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084B\u0000"+ + "\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003\u026e"+ + "\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ + "\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271\u0272"+ + "\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274\n"+ + "\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ + "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ + "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ + "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ + "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ + "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ + "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005c\u0000\u0000\u0280\u0281"+ + "\u0003z=\u0000\u0281\u0282\u0005d\u0000\u0000\u0282\u0284\u0001\u0000"+ + "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ + "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ + "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ + "\u0000\u0286\u0287\u0005<\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ + "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ + "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ + "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ + "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005c\u0000\u0000\u028f\u029d"+ + "\u0005Y\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005>\u0000"+ + "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ + "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ + "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ + "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005>\u0000\u0000\u0299\u029b"+ + "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ + "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ + "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ + "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ + "d\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ + "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005\\\u0000\u0000"+ + "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005>\u0000\u0000\u02a5\u02a7"+ + "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ + "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ + "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ + "\u0000\u0000\u0000\u02ab\u02ac\u0005]\u0000\u0000\u02ac\u008b\u0001\u0000"+ + "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005=\u0000\u0000"+ + "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ + "\u02dc\u0005H\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ + "e\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ + "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ + "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ + "\u0005a\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005>"+ + "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ + "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ + "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ + "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000"+ + "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005a\u0000\u0000\u02c6"+ + "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005>\u0000\u0000\u02c8\u02ca\u0003"+ + "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ + "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ + "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ + "\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ + "\u0000\u02d0\u02d1\u0005a\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ + "\u02d3\u0005>\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ + "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ + "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ + "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ + "b\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ + "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ + "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ + "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ + "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ + "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ + "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ + "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ + "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ + "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ + "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ + "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ + "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ + "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ + "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ + "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ + "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ + "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005r"+ + "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ + "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ + "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005J\u0000\u0000\u02f9"+ + "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005>\u0000\u0000\u02fb\u02fd\u0003"+ + "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ + "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ + "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ + "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ + "\u0000F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ + "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ + "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ + "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u0218\u0228\u0231\u0239\u023e\u0246"+ + "\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a\u0295"+ + "\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { From b3048824935af1e145eeac1209601f70c04bce00 Mon Sep 17 00:00:00 2001 From: Jan Kuipers <148754765+jan-elastic@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:19:44 +0200 Subject: [PATCH 042/102] Remove optional seed from ES|QL SAMPLE (#128887) * Remove optional seed from ES|QL SAMPLE * make it clear that seed is for testing --- .../xpack/esql/parser/EsqlBaseParser.interp | 2 +- .../xpack/esql/parser/EsqlBaseParser.java | 1084 ++++++++--------- 2 files changed, 533 insertions(+), 553 deletions(-) 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 7638709eb9577..02f18d8fa1726 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 @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 537, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 803, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 542, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 62, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 58, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 98, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 74, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 79, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 62, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 58, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 74, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 59, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 74, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 132, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 62, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 99, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 100, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 74, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 79, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 536, 5, 7, 0, 0, 533, 534, 3, 46, 23, 0, 534, 535, 5, 58, 0, 0, 535, 537, 1, 0, 0, 0, 536, 533, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 3, 132, 66, 0, 539, 540, 5, 79, 0, 0, 540, 541, 3, 60, 30, 0, 541, 119, 1, 0, 0, 0, 542, 543, 5, 18, 0, 0, 543, 544, 3, 148, 74, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 71, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 71, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 67, 0, 0, 555, 556, 5, 99, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 62, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 100, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 68, 0, 0, 568, 570, 5, 71, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 72, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 75, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 71, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 70, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 71, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 77, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 60, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 61, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 99, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 100, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 60, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 99, 0, 0, 655, 669, 5, 89, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 62, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 62, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 100, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 92, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 62, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 93, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 61, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 72, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 101, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 97, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 62, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 97, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 62, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 97, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 62, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 98, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 114, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 74, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 62, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 536, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ No newline at end of file +[4, 1, 139, 770, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 551, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 558, 8, 61, 10, 61, 12, 61, 561, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 568, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 573, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 581, 8, 61, 10, 61, 12, 61, 584, 9, 61, 1, 62, 1, 62, 3, 62, 588, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 595, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 600, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 605, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 615, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 621, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 629, 8, 65, 10, 65, 12, 65, 632, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 642, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 647, 8, 66, 10, 66, 12, 66, 650, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 658, 8, 67, 10, 67, 12, 67, 661, 9, 67, 1, 67, 1, 67, 3, 67, 665, 8, 67, 3, 67, 667, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 677, 8, 69, 10, 69, 12, 69, 680, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 701, 8, 71, 10, 71, 12, 71, 704, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 712, 8, 71, 10, 71, 12, 71, 715, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 723, 8, 71, 10, 71, 12, 71, 726, 9, 71, 1, 71, 1, 71, 3, 71, 730, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 736, 8, 73, 1, 74, 3, 74, 739, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 744, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 763, 8, 80, 10, 80, 12, 80, 766, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 801, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 572, 1, 0, 0, 0, 124, 599, 1, 0, 0, 0, 126, 601, 1, 0, 0, 0, 128, 614, 1, 0, 0, 0, 130, 620, 1, 0, 0, 0, 132, 641, 1, 0, 0, 0, 134, 651, 1, 0, 0, 0, 136, 670, 1, 0, 0, 0, 138, 672, 1, 0, 0, 0, 140, 683, 1, 0, 0, 0, 142, 729, 1, 0, 0, 0, 144, 731, 1, 0, 0, 0, 146, 735, 1, 0, 0, 0, 148, 738, 1, 0, 0, 0, 150, 743, 1, 0, 0, 0, 152, 747, 1, 0, 0, 0, 154, 749, 1, 0, 0, 0, 156, 751, 1, 0, 0, 0, 158, 756, 1, 0, 0, 0, 160, 758, 1, 0, 0, 0, 162, 767, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 542, 3, 148, 74, 0, 542, 121, 1, 0, 0, 0, 543, 544, 6, 61, -1, 0, 544, 545, 5, 72, 0, 0, 545, 573, 3, 122, 61, 8, 546, 573, 3, 128, 64, 0, 547, 573, 3, 124, 62, 0, 548, 550, 3, 128, 64, 0, 549, 551, 5, 72, 0, 0, 550, 549, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 5, 68, 0, 0, 553, 554, 5, 100, 0, 0, 554, 559, 3, 128, 64, 0, 555, 556, 5, 63, 0, 0, 556, 558, 3, 128, 64, 0, 557, 555, 1, 0, 0, 0, 558, 561, 1, 0, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 562, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 562, 563, 5, 101, 0, 0, 563, 573, 1, 0, 0, 0, 564, 565, 3, 128, 64, 0, 565, 567, 5, 69, 0, 0, 566, 568, 5, 72, 0, 0, 567, 566, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 5, 73, 0, 0, 570, 573, 1, 0, 0, 0, 571, 573, 3, 126, 63, 0, 572, 543, 1, 0, 0, 0, 572, 546, 1, 0, 0, 0, 572, 547, 1, 0, 0, 0, 572, 548, 1, 0, 0, 0, 572, 564, 1, 0, 0, 0, 572, 571, 1, 0, 0, 0, 573, 582, 1, 0, 0, 0, 574, 575, 10, 5, 0, 0, 575, 576, 5, 56, 0, 0, 576, 581, 3, 122, 61, 6, 577, 578, 10, 4, 0, 0, 578, 579, 5, 76, 0, 0, 579, 581, 3, 122, 61, 5, 580, 574, 1, 0, 0, 0, 580, 577, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 123, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 3, 128, 64, 0, 586, 588, 5, 72, 0, 0, 587, 586, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 5, 71, 0, 0, 590, 591, 3, 152, 76, 0, 591, 600, 1, 0, 0, 0, 592, 594, 3, 128, 64, 0, 593, 595, 5, 72, 0, 0, 594, 593, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 5, 78, 0, 0, 597, 598, 3, 152, 76, 0, 598, 600, 1, 0, 0, 0, 599, 585, 1, 0, 0, 0, 599, 592, 1, 0, 0, 0, 600, 125, 1, 0, 0, 0, 601, 604, 3, 46, 23, 0, 602, 603, 5, 61, 0, 0, 603, 605, 3, 10, 5, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 5, 62, 0, 0, 607, 608, 3, 142, 71, 0, 608, 127, 1, 0, 0, 0, 609, 615, 3, 130, 65, 0, 610, 611, 3, 130, 65, 0, 611, 612, 3, 154, 77, 0, 612, 613, 3, 130, 65, 0, 613, 615, 1, 0, 0, 0, 614, 609, 1, 0, 0, 0, 614, 610, 1, 0, 0, 0, 615, 129, 1, 0, 0, 0, 616, 617, 6, 65, -1, 0, 617, 621, 3, 132, 66, 0, 618, 619, 7, 4, 0, 0, 619, 621, 3, 130, 65, 3, 620, 616, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 630, 1, 0, 0, 0, 622, 623, 10, 2, 0, 0, 623, 624, 7, 5, 0, 0, 624, 629, 3, 130, 65, 3, 625, 626, 10, 1, 0, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 2, 628, 622, 1, 0, 0, 0, 628, 625, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 131, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 6, 66, -1, 0, 634, 642, 3, 142, 71, 0, 635, 642, 3, 46, 23, 0, 636, 642, 3, 134, 67, 0, 637, 638, 5, 100, 0, 0, 638, 639, 3, 122, 61, 0, 639, 640, 5, 101, 0, 0, 640, 642, 1, 0, 0, 0, 641, 633, 1, 0, 0, 0, 641, 635, 1, 0, 0, 0, 641, 636, 1, 0, 0, 0, 641, 637, 1, 0, 0, 0, 642, 648, 1, 0, 0, 0, 643, 644, 10, 1, 0, 0, 644, 645, 5, 61, 0, 0, 645, 647, 3, 10, 5, 0, 646, 643, 1, 0, 0, 0, 647, 650, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 133, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 651, 652, 3, 136, 68, 0, 652, 666, 5, 100, 0, 0, 653, 667, 5, 90, 0, 0, 654, 659, 3, 122, 61, 0, 655, 656, 5, 63, 0, 0, 656, 658, 3, 122, 61, 0, 657, 655, 1, 0, 0, 0, 658, 661, 1, 0, 0, 0, 659, 657, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 664, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 662, 663, 5, 63, 0, 0, 663, 665, 3, 138, 69, 0, 664, 662, 1, 0, 0, 0, 664, 665, 1, 0, 0, 0, 665, 667, 1, 0, 0, 0, 666, 653, 1, 0, 0, 0, 666, 654, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 669, 5, 101, 0, 0, 669, 135, 1, 0, 0, 0, 670, 671, 3, 60, 30, 0, 671, 137, 1, 0, 0, 0, 672, 673, 5, 93, 0, 0, 673, 678, 3, 140, 70, 0, 674, 675, 5, 63, 0, 0, 675, 677, 3, 140, 70, 0, 676, 674, 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, 682, 5, 94, 0, 0, 682, 139, 1, 0, 0, 0, 683, 684, 3, 152, 76, 0, 684, 685, 5, 62, 0, 0, 685, 686, 3, 142, 71, 0, 686, 141, 1, 0, 0, 0, 687, 730, 5, 73, 0, 0, 688, 689, 3, 150, 75, 0, 689, 690, 5, 102, 0, 0, 690, 730, 1, 0, 0, 0, 691, 730, 3, 148, 74, 0, 692, 730, 3, 150, 75, 0, 693, 730, 3, 144, 72, 0, 694, 730, 3, 56, 28, 0, 695, 730, 3, 152, 76, 0, 696, 697, 5, 98, 0, 0, 697, 702, 3, 146, 73, 0, 698, 699, 5, 63, 0, 0, 699, 701, 3, 146, 73, 0, 700, 698, 1, 0, 0, 0, 701, 704, 1, 0, 0, 0, 702, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 705, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 730, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 713, 3, 144, 72, 0, 709, 710, 5, 63, 0, 0, 710, 712, 3, 144, 72, 0, 711, 709, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 99, 0, 0, 717, 730, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 724, 3, 152, 76, 0, 720, 721, 5, 63, 0, 0, 721, 723, 3, 152, 76, 0, 722, 720, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 727, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 728, 5, 99, 0, 0, 728, 730, 1, 0, 0, 0, 729, 687, 1, 0, 0, 0, 729, 688, 1, 0, 0, 0, 729, 691, 1, 0, 0, 0, 729, 692, 1, 0, 0, 0, 729, 693, 1, 0, 0, 0, 729, 694, 1, 0, 0, 0, 729, 695, 1, 0, 0, 0, 729, 696, 1, 0, 0, 0, 729, 707, 1, 0, 0, 0, 729, 718, 1, 0, 0, 0, 730, 143, 1, 0, 0, 0, 731, 732, 7, 6, 0, 0, 732, 145, 1, 0, 0, 0, 733, 736, 3, 148, 74, 0, 734, 736, 3, 150, 75, 0, 735, 733, 1, 0, 0, 0, 735, 734, 1, 0, 0, 0, 736, 147, 1, 0, 0, 0, 737, 739, 7, 4, 0, 0, 738, 737, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 5, 55, 0, 0, 741, 149, 1, 0, 0, 0, 742, 744, 7, 4, 0, 0, 743, 742, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 5, 54, 0, 0, 746, 151, 1, 0, 0, 0, 747, 748, 5, 53, 0, 0, 748, 153, 1, 0, 0, 0, 749, 750, 7, 7, 0, 0, 750, 155, 1, 0, 0, 0, 751, 752, 7, 8, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 3, 158, 79, 0, 754, 755, 3, 160, 80, 0, 755, 157, 1, 0, 0, 0, 756, 757, 3, 28, 14, 0, 757, 159, 1, 0, 0, 0, 758, 759, 5, 75, 0, 0, 759, 764, 3, 162, 81, 0, 760, 761, 5, 63, 0, 0, 761, 763, 3, 162, 81, 0, 762, 760, 1, 0, 0, 0, 763, 766, 1, 0, 0, 0, 764, 762, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 161, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 768, 3, 128, 64, 0, 768, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 550, 559, 567, 572, 580, 582, 587, 594, 599, 604, 614, 620, 628, 630, 641, 648, 659, 664, 666, 678, 702, 713, 724, 729, 735, 738, 743, 764] \ 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 88ce9296910f1..581136657cec6 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 @@ -4505,7 +4505,7 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { { setState(542); match(DEV_SAMPLE); - setState(543); + setState(541); ((SampleCommandContext)_localctx).probability = decimalValue(); } } @@ -4721,19 +4721,18 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(574); + setState(572); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { case 1: { _localctx = new LogicalNotContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(544); match(NOT); - setState(547); + setState(545); booleanExpression(8); } break; @@ -4742,7 +4741,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); + setState(546); valueExpression(); } break; @@ -4751,7 +4750,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(549); + setState(547); regexBooleanExpression(); } break; @@ -4760,41 +4759,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(550); + setState(548); valueExpression(); - setState(552); + setState(550); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(551); + setState(549); match(NOT); } } - setState(554); + setState(552); match(IN); - setState(555); + setState(553); match(LP); - setState(556); + setState(554); valueExpression(); - setState(561); + setState(559); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(557); + setState(555); match(COMMA); - setState(558); + setState(556); valueExpression(); } } - setState(563); + setState(561); _errHandler.sync(this); _la = _input.LA(1); } - setState(564); + setState(562); match(RP); } break; @@ -4803,21 +4802,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(566); + setState(564); valueExpression(); - setState(567); + setState(565); match(IS); - setState(569); + setState(567); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(568); + setState(566); match(NOT); } } - setState(571); + setState(569); match(NULL); } break; @@ -4826,35 +4825,33 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(573); + setState(571); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(584); + setState(582); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); - _alt = getInterpreter().adaptivePredict(_input,47,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(582); + setState(580); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: { _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(576); + setState(574); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(577); + setState(575); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(578); + setState(576); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4863,21 +4860,20 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(579); + setState(577); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(580); + setState(578); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(581); + setState(579); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(586); + setState(584); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); - _alt = getInterpreter().adaptivePredict(_input,47,_ctx); } } } @@ -4930,49 +4926,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(601); + setState(599); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(587); + setState(585); valueExpression(); - setState(589); + setState(587); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(588); + setState(586); match(NOT); } } - setState(591); + setState(589); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(592); + setState(590); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(594); + setState(592); valueExpression(); - setState(596); + setState(594); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(595); + setState(593); match(NOT); } } - setState(598); + setState(596); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(599); + setState(597); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5032,23 +5027,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(603); + setState(601); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(606); + setState(604); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(604); + setState(602); match(CAST_OP); - setState(605); + setState(603); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(608); + setState(606); match(COLON); - setState(609); + setState(607); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5132,15 +5127,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(616); + setState(614); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(611); + setState(609); operatorExpression(0); } break; @@ -5148,11 +5142,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(612); + setState(610); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(613); + setState(611); comparisonOperator(); - setState(614); + setState(612); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5277,17 +5271,16 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(622); + setState(620); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { case 1: { _localctx = new OperatorExpressionDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(619); + setState(617); primaryExpression(0); } break; @@ -5296,7 +5289,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(620); + setState(618); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5307,33 +5300,31 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(621); + setState(619); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(632); + setState(630); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); - _alt = getInterpreter().adaptivePredict(_input,55,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(630); + setState(628); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { case 1: { _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState)); ((ArithmeticBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_operatorExpression); - setState(624); + setState(622); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(625); + setState(623); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 89)) & ~0x3f) == 0 && ((1L << (_la - 89)) & 7L) != 0)) ) { @@ -5344,7 +5335,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(626); + setState(624); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5353,9 +5344,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(627); + setState(625); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(628); + setState(626); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5366,17 +5357,16 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(629); + setState(627); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(634); + setState(632); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); - _alt = getInterpreter().adaptivePredict(_input,55,_ctx); } } } @@ -5532,17 +5522,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(643); + setState(641); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { case 1: { _localctx = new ConstantDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(634); constant(); } break; @@ -5551,7 +5540,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(635); qualifiedName(); } break; @@ -5560,7 +5549,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(638); + setState(636); functionExpression(); } break; @@ -5569,20 +5558,19 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(639); + setState(637); match(LP); - setState(640); + setState(638); booleanExpression(0); - setState(641); + setState(639); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(650); + setState(648); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); - _alt = getInterpreter().adaptivePredict(_input,57,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); @@ -5591,19 +5579,18 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(645); + setState(643); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(646); + setState(644); match(CAST_OP); - setState(647); + setState(645); dataType(); } } } - setState(652); + setState(650); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); - _alt = getInterpreter().adaptivePredict(_input,57,_ctx); } } } @@ -5667,16 +5654,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(653); + setState(651); functionName(); - setState(654); + setState(652); match(LP); - setState(668); + setState(666); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(655); + setState(653); match(ASTERISK); } break; @@ -5699,36 +5686,34 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(656); + setState(654); booleanExpression(0); - setState(661); + setState(659); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); - _alt = getInterpreter().adaptivePredict(_input,58,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(657); + setState(655); match(COMMA); - setState(658); + setState(656); booleanExpression(0); } } } - setState(663); + setState(661); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); - _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } - setState(666); + setState(664); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(664); + setState(662); match(COMMA); - setState(665); + setState(663); mapExpression(); } } @@ -5741,7 +5726,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(670); + setState(668); match(RP); } } @@ -5787,7 +5772,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(670); identifierOrParameter(); } } @@ -5843,27 +5828,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(674); + setState(672); match(LEFT_BRACES); - setState(675); + setState(673); entryExpression(); - setState(680); + setState(678); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(676); + setState(674); match(COMMA); - setState(677); + setState(675); entryExpression(); } } - setState(682); + setState(680); _errHandler.sync(this); _la = _input.LA(1); } - setState(683); + setState(681); match(RIGHT_BRACES); } } @@ -5915,11 +5900,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(685); + setState(683); ((EntryExpressionContext)_localctx).key = string(); - setState(686); + setState(684); match(COLON); - setState(687); + setState(685); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6190,15 +6175,14 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(731); + setState(729); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(689); + setState(687); match(NULL); } break; @@ -6206,9 +6190,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(690); + setState(688); integerValue(); - setState(691); + setState(689); match(UNQUOTED_IDENTIFIER); } break; @@ -6216,7 +6200,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(693); + setState(691); decimalValue(); } break; @@ -6224,7 +6208,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(694); + setState(692); integerValue(); } break; @@ -6232,7 +6216,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(695); + setState(693); booleanValue(); } break; @@ -6240,7 +6224,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(696); + setState(694); parameter(); } break; @@ -6248,7 +6232,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(697); + setState(695); string(); } break; @@ -6256,27 +6240,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(698); + setState(696); match(OPENING_BRACKET); - setState(699); + setState(697); numericValue(); - setState(704); + setState(702); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(700); + setState(698); match(COMMA); - setState(701); + setState(699); numericValue(); } } - setState(706); + setState(704); _errHandler.sync(this); _la = _input.LA(1); } - setState(707); + setState(705); match(CLOSING_BRACKET); } break; @@ -6284,27 +6268,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(709); + setState(707); match(OPENING_BRACKET); - setState(710); + setState(708); booleanValue(); - setState(715); + setState(713); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(711); + setState(709); match(COMMA); - setState(712); + setState(710); booleanValue(); } } - setState(717); + setState(715); _errHandler.sync(this); _la = _input.LA(1); } - setState(718); + setState(716); match(CLOSING_BRACKET); } break; @@ -6312,27 +6296,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(720); + setState(718); match(OPENING_BRACKET); - setState(721); + setState(719); string(); - setState(726); + setState(724); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(722); + setState(720); match(COMMA); - setState(723); + setState(721); string(); } } - setState(728); + setState(726); _errHandler.sync(this); _la = _input.LA(1); } - setState(729); + setState(727); match(CLOSING_BRACKET); } break; @@ -6380,7 +6364,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(733); + setState(731); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6435,21 +6419,20 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(737); + setState(735); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { - switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(735); + setState(733); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(736); + setState(734); integerValue(); } break; @@ -6498,12 +6481,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(740); + setState(738); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(739); + setState(737); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6516,7 +6499,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(742); + setState(740); match(DECIMAL_LITERAL); } } @@ -6563,12 +6546,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(745); + setState(743); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(744); + setState(742); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6581,7 +6564,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(747); + setState(745); match(INTEGER_LITERAL); } } @@ -6625,7 +6608,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(747); match(QUOTED_STRING); } } @@ -6675,7 +6658,7 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(749); _la = _input.LA(1); if ( !(((((_la - 80)) & ~0x3f) == 0 && ((1L << (_la - 80)) & 125L) != 0)) ) { _errHandler.recoverInline(this); @@ -6738,7 +6721,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(753); + setState(751); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6749,11 +6732,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(754); + setState(752); match(JOIN); - setState(755); + setState(753); joinTarget(); - setState(756); + setState(754); joinCondition(); } } @@ -6800,7 +6783,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(758); + setState(756); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6855,29 +6838,27 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(760); + setState(758); match(ON); - setState(761); + setState(759); joinPredicate(); - setState(766); + setState(764); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); - _alt = getInterpreter().adaptivePredict(_input,69,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(762); + setState(760); match(COMMA); - setState(763); + setState(761); joinPredicate(); } } } - setState(768); + setState(766); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); - _alt = getInterpreter().adaptivePredict(_input,69,_ctx); } } } @@ -6923,7 +6904,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(769); + setState(767); valueExpression(); } } @@ -7024,7 +7005,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0304\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u0302\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"+ @@ -7092,243 +7073,243 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "5\u01f0\b5\u000b5\f5\u01f1\u00016\u00016\u00016\u00016\u00017\u00017\u0001"+ "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ - ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0003"+ - ";\u0219\b;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001<\u0001<\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ - ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ - ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ - "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ - "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ - "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ - "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ - "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ - "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ - "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ - "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ - "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ - "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ - "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ - "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ - "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ - "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ - "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ - "5kk\u0001\u0000ef\u0002\u000099??\u0002\u0000BBEE\u0001\u0000WX\u0001"+ - "\u0000Y[\u0002\u0000AANN\u0002\u0000PPRV\u0002\u0000\u0016\u0016\u0018"+ - "\u0019\u0323\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ - "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ - "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ - "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ - "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ - "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ - "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ - "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ - " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ - "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ - "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ - ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ - "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ - "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ - "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ - "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ - "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ - "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ - "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ - "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ - "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ - "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ - "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ - "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ - "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ - "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ - "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021e"+ - "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ - "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ - "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ - "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ - "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ - "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ - "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ - "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ - "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ - "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ - "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ - "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ - "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ - "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ - "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ - "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ - "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ - "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ - "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ - "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ - "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ - "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ - "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ - "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ - " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ - "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ - "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ - "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ - "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ - "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ - "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ - "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ - "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ - "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ - "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ - "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ - "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ - "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ - "\u0000\u00e2\u00e3\u0005>\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ - "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ - "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ - "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005:\u0000\u0000\u00eb\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ - "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ - "\n\u0000\u00f1\u00f2\u0005>\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ - "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ - "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ - "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005:\u0000\u0000\u00fa\u00fc"+ - "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ - "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ - "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ - "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ - "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ - "\u0104\u0105\u0005>\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ - "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ - "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ - "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ - "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ - "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ - "\u0003\u001e\u000f\u0000\u010f\u0110\u0005=\u0000\u0000\u0110\u0112\u0001"+ - "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ - "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ - "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005<\u0000"+ - "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ - "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ - "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ - "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ - "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ - "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ - "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005j\u0000\u0000\u0122\u0127\u0005"+ - "k\u0000\u0000\u0123\u0124\u0005>\u0000\u0000\u0124\u0126\u0005k\u0000"+ - "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ - "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ - "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ - "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ - "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ - "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ - "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ - ";\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ - "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ - "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005>\u0000\u0000\u0137"+ - "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ - "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ - "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ - "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ - "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ - "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ - "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005@\u0000\u0000\u0144\u0146\u0003"+ - "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ - "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ - "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ - "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005@\u0000\u0000\u014c"+ - "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ - "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005>\u0000"+ - "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ - "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ - "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ - "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ - "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160"+ - "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ - "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ - "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000"+ - "\u0162\u0164\u0005_\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ - "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ - "\u0005^\u0000\u0000\u0166\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000"+ - "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ - "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ - "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ - "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ - "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ - "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ - "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174"+ - "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ - "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ - "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ - "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ - "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ - "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f"+ - "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ - "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ - "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ - "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ - "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ - "\u018e\u0003J%\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003"+ - "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ - "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ - "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ - "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193"+ - "\u0194\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005"+ - "\b\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098"+ - "L\u0000\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000"+ - "\u0199\u019a\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b"+ - "\u019c\u0005\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e"+ - "\u0003\u0098L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b"+ - "\u0000\u0000\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000"+ - "\u01a2\u01a7\u0003T*\u0000\u01a3\u01a4\u0005>\u0000\u0000\u01a4\u01a6"+ - "\u0003T*\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\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000"+ - "\u0000\u01aa\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005:\u0000\u0000\u01ac"+ - "\u01ad\u0003\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005"+ - "\u0006\u0000\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000"+ - "\u0000\u01b1\u01b2\u0005a\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000"+ - "\u01b3\u01b4\u0005b\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6"+ - "\u0005!\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000"+ - "\u0000\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000"+ - "\u0000\u01ba\u01bb\u0005J\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc"+ - "\u01ba\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd"+ - "\u01c7\u0001\u0000\u0000\u0000\u01be\u01bf\u0005O\u0000\u0000\u01bf\u01c4"+ - "\u0003^/\u0000\u01c0\u01c1\u0005>\u0000\u0000\u01c1\u01c3\u0003^/\u0000"+ - "\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000"+ - "\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000"+ - "\u01c5\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000"+ - "\u01c7\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000"+ - "\u01c8]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb"+ - "\u0005:\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001"+ - "\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001"+ - "\u0000\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000"+ - "\u0000\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e"+ - "\u0000\u01d2\u01d3\u0005J\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4"+ - "a\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ - "\u0003*\u0015\u0000\u01d7\u01d8\u0005;\u0000\u0000\u01d8\u01da\u0003\u000e"+ + ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0001"+ + ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0003=\u0227\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ + "=\u0005=\u022e\b=\n=\f=\u0231\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003"+ + "=\u0238\b=\u0001=\u0001=\u0001=\u0003=\u023d\b=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0005=\u0245\b=\n=\f=\u0248\t=\u0001>\u0001>\u0003>\u024c"+ + "\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0253\b>\u0001>\u0001>\u0001"+ + ">\u0003>\u0258\b>\u0001?\u0001?\u0001?\u0003?\u025d\b?\u0001?\u0001?\u0001"+ + "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0267\b@\u0001A\u0001A\u0001"+ + "A\u0001A\u0003A\u026d\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ + "A\u0275\bA\nA\fA\u0278\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0003B\u0282\bB\u0001B\u0001B\u0001B\u0005B\u0287\bB\nB\fB\u028a"+ + "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0292\bC\nC\fC\u0295"+ + "\tC\u0001C\u0001C\u0003C\u0299\bC\u0003C\u029b\bC\u0001C\u0001C\u0001"+ + "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a5\bE\nE\fE\u02a8\tE\u0001"+ + "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02bd"+ + "\bG\nG\fG\u02c0\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c8"+ + "\bG\nG\fG\u02cb\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d3"+ + "\bG\nG\fG\u02d6\tG\u0001G\u0001G\u0003G\u02da\bG\u0001H\u0001H\u0001I"+ + "\u0001I\u0003I\u02e0\bI\u0001J\u0003J\u02e3\bJ\u0001J\u0001J\u0001K\u0003"+ + "K\u02e8\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ + "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u02fb"+ + "\bP\nP\fP\u02fe\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ + "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ + "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ + "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ + "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055ll\u0001\u0000fg\u0002\u0000"+ + "::@@\u0002\u0000CCFF\u0001\u0000XY\u0001\u0000Z\\\u0002\u0000BBOO\u0002"+ + "\u0000QQSW\u0002\u0000\u0016\u0016\u0018\u0019\u0321\u0000\u00a4\u0001"+ + "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ + "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ + "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ + "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ + "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ + "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ + "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ + "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ + "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ + "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ + "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ + "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ + "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ + "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ + ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ + "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ + "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0191\u0001\u0000\u0000\u0000"+ + "L\u0195\u0001\u0000\u0000\u0000N\u019b\u0001\u0000\u0000\u0000P\u019f"+ + "\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000\u0000\u0000T\u01aa\u0001\u0000"+ + "\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000X\u01b1\u0001\u0000\u0000\u0000"+ + "Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8\u0001\u0000\u0000\u0000^\u01cc"+ + "\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000\u0000\u0000b\u01d5\u0001\u0000"+ + "\u0000\u0000d\u01db\u0001\u0000\u0000\u0000f\u01e8\u0001\u0000\u0000\u0000"+ + "h\u01eb\u0001\u0000\u0000\u0000j\u01ef\u0001\u0000\u0000\u0000l\u01f3"+ + "\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000\u0000\u0000p\u0208\u0001\u0000"+ + "\u0000\u0000r\u020a\u0001\u0000\u0000\u0000t\u020c\u0001\u0000\u0000\u0000"+ + "v\u0214\u0001\u0000\u0000\u0000x\u021c\u0001\u0000\u0000\u0000z\u023c"+ + "\u0001\u0000\u0000\u0000|\u0257\u0001\u0000\u0000\u0000~\u0259\u0001\u0000"+ + "\u0000\u0000\u0080\u0266\u0001\u0000\u0000\u0000\u0082\u026c\u0001\u0000"+ + "\u0000\u0000\u0084\u0281\u0001\u0000\u0000\u0000\u0086\u028b\u0001\u0000"+ + "\u0000\u0000\u0088\u029e\u0001\u0000\u0000\u0000\u008a\u02a0\u0001\u0000"+ + "\u0000\u0000\u008c\u02ab\u0001\u0000\u0000\u0000\u008e\u02d9\u0001\u0000"+ + "\u0000\u0000\u0090\u02db\u0001\u0000\u0000\u0000\u0092\u02df\u0001\u0000"+ + "\u0000\u0000\u0094\u02e2\u0001\u0000\u0000\u0000\u0096\u02e7\u0001\u0000"+ + "\u0000\u0000\u0098\u02eb\u0001\u0000\u0000\u0000\u009a\u02ed\u0001\u0000"+ + "\u0000\u0000\u009c\u02ef\u0001\u0000\u0000\u0000\u009e\u02f4\u0001\u0000"+ + "\u0000\u0000\u00a0\u02f6\u0001\u0000\u0000\u0000\u00a2\u02ff\u0001\u0000"+ + "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ + "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ + "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ + "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ + "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ + "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ + "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ + "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ + "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ + "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ + "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ + "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ + "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ + "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ + "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ + "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ + "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ + "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ + "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ + "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ + "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ + "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ + "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ + "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ + "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ + "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ + "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ + "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ + "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005?\u0000"+ + "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ + "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ + "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ + "\u00eb\u0005;\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ + "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ + "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ + "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005?\u0000"+ + "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ + "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ + "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ + "\u00fa\u0005;\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ + "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ + "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ + "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ + "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ + "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005?\u0000\u0000"+ + "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ + "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ + "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ + "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ + "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ + "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ + "\u0110\u0005>\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ + "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ + "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ + "\"\u0011\u0000\u0115\u0116\u0005=\u0000\u0000\u0116\u0118\u0003 \u0010"+ + "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ + "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ + "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ + "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ + "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ + "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ + "\u0122\u0005k\u0000\u0000\u0122\u0127\u0005l\u0000\u0000\u0123\u0124\u0005"+ + "?\u0000\u0000\u0124\u0126\u0005l\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ + "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ + "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ + "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ + "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ + "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ + "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ + "\u0000\u0000\u0000\u0131\u0132\u0005<\u0000\u0000\u0132\u0134\u0003\u000e"+ + "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ + "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ + "\u0136\u0137\u0005?\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ + "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ + "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ + "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ + "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ + "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ + "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ + "\u0144\u0005A\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ + "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ + "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ + "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ + "\u0000\u014b\u014c\u0005A\u0000\u0000\u014c\u014e\u00036\u001b\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"+ + "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ + "\u00030\u0018\u0000\u0153\u0154\u0005?\u0000\u0000\u0154\u0156\u00030"+ + "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ + "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ + "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ + "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ + "\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ + "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ + "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ + "\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000\u0162\u0164\u0005`\u0000"+ + "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ + "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005_\u0000\u0000\u0166"+ + "\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ + "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ + "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ + "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ + "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ + "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ + "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ + "\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174\u0176\u0003B!\u0000"+ + "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ + "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ + "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ + "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ + "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ + "\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f\u0181\u0007\u0003"+ + "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ + "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ + "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ + "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ + "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ + "\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ + "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ + "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ + "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ + "\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194\u00030"+ + "\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b\u0000"+ + "\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000\u0198"+ + "\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a\u0001"+ + "\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005\n"+ + "\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098L\u0000"+ + "\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000\u01a0"+ + "\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7\u0003"+ + "T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ + "S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa\u01ab"+ + "\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003\u008e"+ + "G\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000\u0000"+ + "\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1\u01b2"+ + "\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4\u0005"+ + "c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!\u0000"+ + "\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000\u0000"+ + "\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000\u01ba"+ + "\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba\u0001"+ + "\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7\u0001"+ + "\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003^/"+ + "\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ + "\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000\u01c4"+ + "\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+ + "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7"+ + "\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8"+ + "]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb\u0005"+ + ";\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001\u0000"+ + "\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001\u0000"+ + "\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000\u0000"+ + "\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e\u0000"+ + "\u01d2\u01d3\u0005K\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4a"+ + "\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ + "\u0003*\u0015\u0000\u01d7\u01d8\u0005<\u0000\u0000\u01d8\u01da\u0003\u000e"+ "\u0007\u0000\u01d9\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001\u0000"+ "\u0000\u0000\u01dac\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0004\u0000"+ "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005J\u0000\u0000\u01de"+ @@ -7361,144 +7342,143 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u020f\u0005J\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ "\u0005O\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ "\u0000\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213u\u0001\u0000\u0000"+ - "\u0000\u0214\u0218\u0005\u0007\u0000\u0000\u0215\u0216\u0003.\u0017\u0000"+ - "\u0216\u0217\u0005:\u0000\u0000\u0217\u0219\u0001\u0000\u0000\u0000\u0218"+ - "\u0215\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219"+ - "\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0003\u0084B\u0000\u021b\u021c"+ - "\u0005O\u0000\u0000\u021c\u021d\u0003<\u001e\u0000\u021dw\u0001\u0000"+ - "\u0000\u0000\u021e\u021f\u0005\u0012\u0000\u0000\u021f\u0220\u0003\u0094"+ - "J\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222\u0006=\uffff\uffff"+ - "\u0000\u0222\u0223\u0005G\u0000\u0000\u0223\u023f\u0003z=\b\u0224\u023f"+ - "\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228\u0003\u0080"+ - "@\u0000\u0227\u0229\u0005G\u0000\u0000\u0228\u0227\u0001\u0000\u0000\u0000"+ - "\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000\u0000\u0000"+ - "\u022a\u022b\u0005C\u0000\u0000\u022b\u022c\u0005c\u0000\u0000\u022c\u0231"+ - "\u0003\u0080@\u0000\u022d\u022e\u0005>\u0000\u0000\u022e\u0230\u0003\u0080"+ - "@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001\u0000\u0000"+ - "\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001\u0000\u0000"+ - "\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001\u0000\u0000"+ - "\u0000\u0234\u0235\u0005d\u0000\u0000\u0235\u023f\u0001\u0000\u0000\u0000"+ - "\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005D\u0000\u0000\u0238\u023a"+ - "\u0005G\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239\u023a\u0001"+ - "\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0005"+ - "H\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f\u0003~?"+ - "\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000\u0000"+ - "\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000\u0000"+ - "\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000\u0000"+ - "\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000\u0000"+ - "\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243\u0244"+ - "\n\u0004\u0000\u0000\u0244\u0245\u0005K\u0000\u0000\u0245\u0247\u0003"+ - "z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000\u0000"+ - "\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000"+ - "\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000\u0000"+ - "\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000\u024c"+ - "\u024e\u0005G\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e"+ - "\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250"+ - "\u0005F\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001\u0000"+ - "\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005G\u0000\u0000"+ - "\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000"+ - "\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005M\u0000\u0000\u0257"+ - "\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259\u024b"+ - "\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a}\u0001"+ - "\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005<\u0000"+ - "\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000\u0000\u0000"+ - "\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000\u0000\u0000"+ - "\u0260\u0261\u0005=\u0000\u0000\u0261\u0262\u0003\u008eG\u0000\u0262\u007f"+ - "\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264\u0265\u0003"+ - "\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003\u0082A"+ - "\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000\u0000"+ - "\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000\u0000"+ - "\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084B\u0000"+ - "\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003\u026e"+ - "\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ - "\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271\u0272"+ - "\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274\n"+ - "\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ - "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ - "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ - "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ - "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ - "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ - "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005c\u0000\u0000\u0280\u0281"+ - "\u0003z=\u0000\u0281\u0282\u0005d\u0000\u0000\u0282\u0284\u0001\u0000"+ - "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ - "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ - "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ - "\u0000\u0286\u0287\u0005<\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ - "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ - "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ - "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ - "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005c\u0000\u0000\u028f\u029d"+ - "\u0005Y\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005>\u0000"+ - "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ - "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ - "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ - "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005>\u0000\u0000\u0299\u029b"+ - "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ - "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ - "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ - "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ - "d\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ - "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005\\\u0000\u0000"+ - "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005>\u0000\u0000\u02a5\u02a7"+ - "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ - "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ - "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ - "\u0000\u0000\u0000\u02ab\u02ac\u0005]\u0000\u0000\u02ac\u008b\u0001\u0000"+ - "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005=\u0000\u0000"+ - "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ - "\u02dc\u0005H\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ - "e\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ - "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ - "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ - "\u0005a\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005>"+ - "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ - "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ - "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ - "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000"+ - "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005a\u0000\u0000\u02c6"+ - "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005>\u0000\u0000\u02c8\u02ca\u0003"+ - "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ - "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ - "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ - "\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ - "\u0000\u02d0\u02d1\u0005a\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ - "\u02d3\u0005>\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ - "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ - "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ - "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ - "b\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ - "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ - "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ - "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ - "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ - "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ - "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ - "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ - "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ - "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ - "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ - "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ - "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ - "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ - "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ - "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ - "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ - "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005r"+ - "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ - "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ - "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005J\u0000\u0000\u02f9"+ - "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005>\u0000\u0000\u02fb\u02fd\u0003"+ - "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ - "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ - "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ - "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ - "\u0000F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ - "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ - "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ - "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u0218\u0228\u0231\u0239\u023e\u0246"+ - "\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a\u0295"+ - "\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; + "\u0000\u0214\u0215\u0005\u0007\u0000\u0000\u0215\u0216\u0003\u0084B\u0000"+ + "\u0216\u0217\u0005P\u0000\u0000\u0217\u021a\u0003<\u001e\u0000\u0218\u0219"+ + "\u00059\u0000\u0000\u0219\u021b\u0003.\u0017\u0000\u021a\u0218\u0001\u0000"+ + "\u0000\u0000\u021a\u021b\u0001\u0000\u0000\u0000\u021bw\u0001\u0000\u0000"+ + "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021e\u0003\u0094J\u0000"+ + "\u021ey\u0001\u0000\u0000\u0000\u021f\u0220\u0006=\uffff\uffff\u0000\u0220"+ + "\u0221\u0005H\u0000\u0000\u0221\u023d\u0003z=\b\u0222\u023d\u0003\u0080"+ + "@\u0000\u0223\u023d\u0003|>\u0000\u0224\u0226\u0003\u0080@\u0000\u0225"+ + "\u0227\u0005H\u0000\u0000\u0226\u0225\u0001\u0000\u0000\u0000\u0226\u0227"+ + "\u0001\u0000\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228\u0229"+ + "\u0005D\u0000\u0000\u0229\u022a\u0005d\u0000\u0000\u022a\u022f\u0003\u0080"+ + "@\u0000\u022b\u022c\u0005?\u0000\u0000\u022c\u022e\u0003\u0080@\u0000"+ + "\u022d\u022b\u0001\u0000\u0000\u0000\u022e\u0231\u0001\u0000\u0000\u0000"+ + "\u022f\u022d\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000"+ + "\u0230\u0232\u0001\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000"+ + "\u0232\u0233\u0005e\u0000\u0000\u0233\u023d\u0001\u0000\u0000\u0000\u0234"+ + "\u0235\u0003\u0080@\u0000\u0235\u0237\u0005E\u0000\u0000\u0236\u0238\u0005"+ + "H\u0000\u0000\u0237\u0236\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000"+ + "\u0000\u0000\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0005I\u0000"+ + "\u0000\u023a\u023d\u0001\u0000\u0000\u0000\u023b\u023d\u0003~?\u0000\u023c"+ + "\u021f\u0001\u0000\u0000\u0000\u023c\u0222\u0001\u0000\u0000\u0000\u023c"+ + "\u0223\u0001\u0000\u0000\u0000\u023c\u0224\u0001\u0000\u0000\u0000\u023c"+ + "\u0234\u0001\u0000\u0000\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d"+ + "\u0246\u0001\u0000\u0000\u0000\u023e\u023f\n\u0005\u0000\u0000\u023f\u0240"+ + "\u00058\u0000\u0000\u0240\u0245\u0003z=\u0006\u0241\u0242\n\u0004\u0000"+ + "\u0000\u0242\u0243\u0005L\u0000\u0000\u0243\u0245\u0003z=\u0005\u0244"+ + "\u023e\u0001\u0000\u0000\u0000\u0244\u0241\u0001\u0000\u0000\u0000\u0245"+ + "\u0248\u0001\u0000\u0000\u0000\u0246\u0244\u0001\u0000\u0000\u0000\u0246"+ + "\u0247\u0001\u0000\u0000\u0000\u0247{\u0001\u0000\u0000\u0000\u0248\u0246"+ + "\u0001\u0000\u0000\u0000\u0249\u024b\u0003\u0080@\u0000\u024a\u024c\u0005"+ + "H\u0000\u0000\u024b\u024a\u0001\u0000\u0000\u0000\u024b\u024c\u0001\u0000"+ + "\u0000\u0000\u024c\u024d\u0001\u0000\u0000\u0000\u024d\u024e\u0005G\u0000"+ + "\u0000\u024e\u024f\u0003\u0098L\u0000\u024f\u0258\u0001\u0000\u0000\u0000"+ + "\u0250\u0252\u0003\u0080@\u0000\u0251\u0253\u0005H\u0000\u0000\u0252\u0251"+ + "\u0001\u0000\u0000\u0000\u0252\u0253\u0001\u0000\u0000\u0000\u0253\u0254"+ + "\u0001\u0000\u0000\u0000\u0254\u0255\u0005N\u0000\u0000\u0255\u0256\u0003"+ + "\u0098L\u0000\u0256\u0258\u0001\u0000\u0000\u0000\u0257\u0249\u0001\u0000"+ + "\u0000\u0000\u0257\u0250\u0001\u0000\u0000\u0000\u0258}\u0001\u0000\u0000"+ + "\u0000\u0259\u025c\u0003.\u0017\u0000\u025a\u025b\u0005=\u0000\u0000\u025b"+ + "\u025d\u0003\n\u0005\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025c\u025d"+ + "\u0001\u0000\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f"+ + "\u0005>\u0000\u0000\u025f\u0260\u0003\u008eG\u0000\u0260\u007f\u0001\u0000"+ + "\u0000\u0000\u0261\u0267\u0003\u0082A\u0000\u0262\u0263\u0003\u0082A\u0000"+ + "\u0263\u0264\u0003\u009aM\u0000\u0264\u0265\u0003\u0082A\u0000\u0265\u0267"+ + "\u0001\u0000\u0000\u0000\u0266\u0261\u0001\u0000\u0000\u0000\u0266\u0262"+ + "\u0001\u0000\u0000\u0000\u0267\u0081\u0001\u0000\u0000\u0000\u0268\u0269"+ + "\u0006A\uffff\uffff\u0000\u0269\u026d\u0003\u0084B\u0000\u026a\u026b\u0007"+ + "\u0004\u0000\u0000\u026b\u026d\u0003\u0082A\u0003\u026c\u0268\u0001\u0000"+ + "\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026d\u0276\u0001\u0000"+ + "\u0000\u0000\u026e\u026f\n\u0002\u0000\u0000\u026f\u0270\u0007\u0005\u0000"+ + "\u0000\u0270\u0275\u0003\u0082A\u0003\u0271\u0272\n\u0001\u0000\u0000"+ + "\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0002\u0274"+ + "\u026e\u0001\u0000\u0000\u0000\u0274\u0271\u0001\u0000\u0000\u0000\u0275"+ + "\u0278\u0001\u0000\u0000\u0000\u0276\u0274\u0001\u0000\u0000\u0000\u0276"+ + "\u0277\u0001\u0000\u0000\u0000\u0277\u0083\u0001\u0000\u0000\u0000\u0278"+ + "\u0276\u0001\u0000\u0000\u0000\u0279\u027a\u0006B\uffff\uffff\u0000\u027a"+ + "\u0282\u0003\u008eG\u0000\u027b\u0282\u0003.\u0017\u0000\u027c\u0282\u0003"+ + "\u0086C\u0000\u027d\u027e\u0005d\u0000\u0000\u027e\u027f\u0003z=\u0000"+ + "\u027f\u0280\u0005e\u0000\u0000\u0280\u0282\u0001\u0000\u0000\u0000\u0281"+ + "\u0279\u0001\u0000\u0000\u0000\u0281\u027b\u0001\u0000\u0000\u0000\u0281"+ + "\u027c\u0001\u0000\u0000\u0000\u0281\u027d\u0001\u0000\u0000\u0000\u0282"+ + "\u0288\u0001\u0000\u0000\u0000\u0283\u0284\n\u0001\u0000\u0000\u0284\u0285"+ + "\u0005=\u0000\u0000\u0285\u0287\u0003\n\u0005\u0000\u0286\u0283\u0001"+ + "\u0000\u0000\u0000\u0287\u028a\u0001\u0000\u0000\u0000\u0288\u0286\u0001"+ + "\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000\u0289\u0085\u0001"+ + "\u0000\u0000\u0000\u028a\u0288\u0001\u0000\u0000\u0000\u028b\u028c\u0003"+ + "\u0088D\u0000\u028c\u029a\u0005d\u0000\u0000\u028d\u029b\u0005Z\u0000"+ + "\u0000\u028e\u0293\u0003z=\u0000\u028f\u0290\u0005?\u0000\u0000\u0290"+ + "\u0292\u0003z=\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0295\u0001"+ + "\u0000\u0000\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0293\u0294\u0001"+ + "\u0000\u0000\u0000\u0294\u0298\u0001\u0000\u0000\u0000\u0295\u0293\u0001"+ + "\u0000\u0000\u0000\u0296\u0297\u0005?\u0000\u0000\u0297\u0299\u0003\u008a"+ + "E\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0298\u0299\u0001\u0000\u0000"+ + "\u0000\u0299\u029b\u0001\u0000\u0000\u0000\u029a\u028d\u0001\u0000\u0000"+ + "\u0000\u029a\u028e\u0001\u0000\u0000\u0000\u029a\u029b\u0001\u0000\u0000"+ + "\u0000\u029b\u029c\u0001\u0000\u0000\u0000\u029c\u029d\u0005e\u0000\u0000"+ + "\u029d\u0087\u0001\u0000\u0000\u0000\u029e\u029f\u0003<\u001e\u0000\u029f"+ + "\u0089\u0001\u0000\u0000\u0000\u02a0\u02a1\u0005]\u0000\u0000\u02a1\u02a6"+ + "\u0003\u008cF\u0000\u02a2\u02a3\u0005?\u0000\u0000\u02a3\u02a5\u0003\u008c"+ + "F\u0000\u02a4\u02a2\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\u02aa\u0005^\u0000\u0000\u02aa\u008b\u0001\u0000\u0000\u0000"+ + "\u02ab\u02ac\u0003\u0098L\u0000\u02ac\u02ad\u0005>\u0000\u0000\u02ad\u02ae"+ + "\u0003\u008eG\u0000\u02ae\u008d\u0001\u0000\u0000\u0000\u02af\u02da\u0005"+ + "I\u0000\u0000\u02b0\u02b1\u0003\u0096K\u0000\u02b1\u02b2\u0005f\u0000"+ + "\u0000\u02b2\u02da\u0001\u0000\u0000\u0000\u02b3\u02da\u0003\u0094J\u0000"+ + "\u02b4\u02da\u0003\u0096K\u0000\u02b5\u02da\u0003\u0090H\u0000\u02b6\u02da"+ + "\u00038\u001c\u0000\u02b7\u02da\u0003\u0098L\u0000\u02b8\u02b9\u0005b"+ + "\u0000\u0000\u02b9\u02be\u0003\u0092I\u0000\u02ba\u02bb\u0005?\u0000\u0000"+ + "\u02bb\u02bd\u0003\u0092I\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd"+ + "\u02c0\u0001\u0000\u0000\u0000\u02be\u02bc\u0001\u0000\u0000\u0000\u02be"+ + "\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c1\u0001\u0000\u0000\u0000\u02c0"+ + "\u02be\u0001\u0000\u0000\u0000\u02c1\u02c2\u0005c\u0000\u0000\u02c2\u02da"+ + "\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000\u02c4\u02c9\u0003"+ + "\u0090H\u0000\u02c5\u02c6\u0005?\u0000\u0000\u02c6\u02c8\u0003\u0090H"+ + "\u0000\u02c7\u02c5\u0001\u0000\u0000\u0000\u02c8\u02cb\u0001\u0000\u0000"+ + "\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000"+ + "\u0000\u02ca\u02cc\u0001\u0000\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000"+ + "\u0000\u02cc\u02cd\u0005c\u0000\u0000\u02cd\u02da\u0001\u0000\u0000\u0000"+ + "\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02d4\u0003\u0098L\u0000\u02d0\u02d1"+ + "\u0005?\u0000\u0000\u02d1\u02d3\u0003\u0098L\u0000\u02d2\u02d0\u0001\u0000"+ + "\u0000\u0000\u02d3\u02d6\u0001\u0000\u0000\u0000\u02d4\u02d2\u0001\u0000"+ + "\u0000\u0000\u02d4\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d7\u0001\u0000"+ + "\u0000\u0000\u02d6\u02d4\u0001\u0000\u0000\u0000\u02d7\u02d8\u0005c\u0000"+ + "\u0000\u02d8\u02da\u0001\u0000\u0000\u0000\u02d9\u02af\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b0\u0001\u0000\u0000\u0000\u02d9\u02b3\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b4\u0001\u0000\u0000\u0000\u02d9\u02b5\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b6\u0001\u0000\u0000\u0000\u02d9\u02b7\u0001\u0000\u0000"+ + "\u0000\u02d9\u02b8\u0001\u0000\u0000\u0000\u02d9\u02c3\u0001\u0000\u0000"+ + "\u0000\u02d9\u02ce\u0001\u0000\u0000\u0000\u02da\u008f\u0001\u0000\u0000"+ + "\u0000\u02db\u02dc\u0007\u0006\u0000\u0000\u02dc\u0091\u0001\u0000\u0000"+ + "\u0000\u02dd\u02e0\u0003\u0094J\u0000\u02de\u02e0\u0003\u0096K\u0000\u02df"+ + "\u02dd\u0001\u0000\u0000\u0000\u02df\u02de\u0001\u0000\u0000\u0000\u02e0"+ + "\u0093\u0001\u0000\u0000\u0000\u02e1\u02e3\u0007\u0004\u0000\u0000\u02e2"+ + "\u02e1\u0001\u0000\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ + "\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u00057\u0000\u0000\u02e5\u0095"+ + "\u0001\u0000\u0000\u0000\u02e6\u02e8\u0007\u0004\u0000\u0000\u02e7\u02e6"+ + "\u0001\u0000\u0000\u0000\u02e7\u02e8\u0001\u0000\u0000\u0000\u02e8\u02e9"+ + "\u0001\u0000\u0000\u0000\u02e9\u02ea\u00056\u0000\u0000\u02ea\u0097\u0001"+ + "\u0000\u0000\u0000\u02eb\u02ec\u00055\u0000\u0000\u02ec\u0099\u0001\u0000"+ + "\u0000\u0000\u02ed\u02ee\u0007\u0007\u0000\u0000\u02ee\u009b\u0001\u0000"+ + "\u0000\u0000\u02ef\u02f0\u0007\b\u0000\u0000\u02f0\u02f1\u0005s\u0000"+ + "\u0000\u02f1\u02f2\u0003\u009eO\u0000\u02f2\u02f3\u0003\u00a0P\u0000\u02f3"+ + "\u009d\u0001\u0000\u0000\u0000\u02f4\u02f5\u0003\u001c\u000e\u0000\u02f5"+ + "\u009f\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005K\u0000\u0000\u02f7\u02fc"+ + "\u0003\u00a2Q\u0000\u02f8\u02f9\u0005?\u0000\u0000\u02f9\u02fb\u0003\u00a2"+ + "Q\u0000\u02fa\u02f8\u0001\u0000\u0000\u0000\u02fb\u02fe\u0001\u0000\u0000"+ + "\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fc\u02fd\u0001\u0000\u0000"+ + "\u0000\u02fd\u00a1\u0001\u0000\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000"+ + "\u0000\u02ff\u0300\u0003\u0080@\u0000\u0300\u00a3\u0001\u0000\u0000\u0000"+ + "F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117\u0119"+ + "\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167\u016c"+ + "\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9\u01df"+ + "\u01e6\u01f1\u01ff\u0208\u0212\u021a\u0226\u022f\u0237\u023c\u0244\u0246"+ + "\u024b\u0252\u0257\u025c\u0266\u026c\u0274\u0276\u0281\u0288\u0293\u0298"+ + "\u029a\u02a6\u02be\u02c9\u02d4\u02d9\u02df\u02e2\u02e7\u02fc"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { From 219b4249831087a1657edfb5dae1280df059c223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Tue, 10 Jun 2025 17:54:48 +0200 Subject: [PATCH 043/102] ES|QL Completion command syntax change (#129189) --- .../xpack/esql/parser/EsqlBaseParser.interp | 2 +- .../xpack/esql/parser/EsqlBaseParser.java | 1065 +++++++++-------- 2 files changed, 534 insertions(+), 533 deletions(-) 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 02f18d8fa1726..7638709eb9577 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 @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 770, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 539, 8, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 551, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 558, 8, 61, 10, 61, 12, 61, 561, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 568, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 573, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 581, 8, 61, 10, 61, 12, 61, 584, 9, 61, 1, 62, 1, 62, 3, 62, 588, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 595, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 600, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 605, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 615, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 621, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 629, 8, 65, 10, 65, 12, 65, 632, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 642, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 647, 8, 66, 10, 66, 12, 66, 650, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 658, 8, 67, 10, 67, 12, 67, 661, 9, 67, 1, 67, 1, 67, 3, 67, 665, 8, 67, 3, 67, 667, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 677, 8, 69, 10, 69, 12, 69, 680, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 701, 8, 71, 10, 71, 12, 71, 704, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 712, 8, 71, 10, 71, 12, 71, 715, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 723, 8, 71, 10, 71, 12, 71, 726, 9, 71, 1, 71, 1, 71, 3, 71, 730, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 736, 8, 73, 1, 74, 3, 74, 739, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 744, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 763, 8, 80, 10, 80, 12, 80, 766, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 108, 108, 1, 0, 102, 103, 2, 0, 58, 58, 64, 64, 2, 0, 67, 67, 70, 70, 1, 0, 88, 89, 1, 0, 90, 92, 2, 0, 66, 66, 79, 79, 2, 0, 81, 81, 83, 87, 2, 0, 22, 22, 24, 25, 801, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 540, 1, 0, 0, 0, 122, 572, 1, 0, 0, 0, 124, 599, 1, 0, 0, 0, 126, 601, 1, 0, 0, 0, 128, 614, 1, 0, 0, 0, 130, 620, 1, 0, 0, 0, 132, 641, 1, 0, 0, 0, 134, 651, 1, 0, 0, 0, 136, 670, 1, 0, 0, 0, 138, 672, 1, 0, 0, 0, 140, 683, 1, 0, 0, 0, 142, 729, 1, 0, 0, 0, 144, 731, 1, 0, 0, 0, 146, 735, 1, 0, 0, 0, 148, 738, 1, 0, 0, 0, 150, 743, 1, 0, 0, 0, 152, 747, 1, 0, 0, 0, 154, 749, 1, 0, 0, 0, 156, 751, 1, 0, 0, 0, 158, 756, 1, 0, 0, 0, 160, 758, 1, 0, 0, 0, 162, 767, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 63, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 59, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 63, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 59, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 63, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 62, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 61, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 107, 0, 0, 290, 295, 5, 108, 0, 0, 291, 292, 5, 63, 0, 0, 292, 294, 5, 108, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 60, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 63, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 65, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 65, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 63, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 129, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 77, 0, 0, 354, 356, 5, 96, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 95, 0, 0, 358, 360, 5, 97, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 63, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 74, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 63, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 57, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 63, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 59, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 98, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 99, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 75, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 80, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 63, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 59, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 75, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 60, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 75, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 57, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 63, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 100, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 101, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 75, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 80, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 533, 5, 7, 0, 0, 533, 534, 3, 132, 66, 0, 534, 535, 5, 80, 0, 0, 535, 538, 3, 60, 30, 0, 536, 537, 5, 57, 0, 0, 537, 539, 3, 46, 23, 0, 538, 536, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 119, 1, 0, 0, 0, 540, 541, 5, 18, 0, 0, 541, 542, 3, 148, 74, 0, 542, 121, 1, 0, 0, 0, 543, 544, 6, 61, -1, 0, 544, 545, 5, 72, 0, 0, 545, 573, 3, 122, 61, 8, 546, 573, 3, 128, 64, 0, 547, 573, 3, 124, 62, 0, 548, 550, 3, 128, 64, 0, 549, 551, 5, 72, 0, 0, 550, 549, 1, 0, 0, 0, 550, 551, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 553, 5, 68, 0, 0, 553, 554, 5, 100, 0, 0, 554, 559, 3, 128, 64, 0, 555, 556, 5, 63, 0, 0, 556, 558, 3, 128, 64, 0, 557, 555, 1, 0, 0, 0, 558, 561, 1, 0, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 562, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 562, 563, 5, 101, 0, 0, 563, 573, 1, 0, 0, 0, 564, 565, 3, 128, 64, 0, 565, 567, 5, 69, 0, 0, 566, 568, 5, 72, 0, 0, 567, 566, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 5, 73, 0, 0, 570, 573, 1, 0, 0, 0, 571, 573, 3, 126, 63, 0, 572, 543, 1, 0, 0, 0, 572, 546, 1, 0, 0, 0, 572, 547, 1, 0, 0, 0, 572, 548, 1, 0, 0, 0, 572, 564, 1, 0, 0, 0, 572, 571, 1, 0, 0, 0, 573, 582, 1, 0, 0, 0, 574, 575, 10, 5, 0, 0, 575, 576, 5, 56, 0, 0, 576, 581, 3, 122, 61, 6, 577, 578, 10, 4, 0, 0, 578, 579, 5, 76, 0, 0, 579, 581, 3, 122, 61, 5, 580, 574, 1, 0, 0, 0, 580, 577, 1, 0, 0, 0, 581, 584, 1, 0, 0, 0, 582, 580, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 123, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 585, 587, 3, 128, 64, 0, 586, 588, 5, 72, 0, 0, 587, 586, 1, 0, 0, 0, 587, 588, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 590, 5, 71, 0, 0, 590, 591, 3, 152, 76, 0, 591, 600, 1, 0, 0, 0, 592, 594, 3, 128, 64, 0, 593, 595, 5, 72, 0, 0, 594, 593, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 5, 78, 0, 0, 597, 598, 3, 152, 76, 0, 598, 600, 1, 0, 0, 0, 599, 585, 1, 0, 0, 0, 599, 592, 1, 0, 0, 0, 600, 125, 1, 0, 0, 0, 601, 604, 3, 46, 23, 0, 602, 603, 5, 61, 0, 0, 603, 605, 3, 10, 5, 0, 604, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 5, 62, 0, 0, 607, 608, 3, 142, 71, 0, 608, 127, 1, 0, 0, 0, 609, 615, 3, 130, 65, 0, 610, 611, 3, 130, 65, 0, 611, 612, 3, 154, 77, 0, 612, 613, 3, 130, 65, 0, 613, 615, 1, 0, 0, 0, 614, 609, 1, 0, 0, 0, 614, 610, 1, 0, 0, 0, 615, 129, 1, 0, 0, 0, 616, 617, 6, 65, -1, 0, 617, 621, 3, 132, 66, 0, 618, 619, 7, 4, 0, 0, 619, 621, 3, 130, 65, 3, 620, 616, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 621, 630, 1, 0, 0, 0, 622, 623, 10, 2, 0, 0, 623, 624, 7, 5, 0, 0, 624, 629, 3, 130, 65, 3, 625, 626, 10, 1, 0, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 2, 628, 622, 1, 0, 0, 0, 628, 625, 1, 0, 0, 0, 629, 632, 1, 0, 0, 0, 630, 628, 1, 0, 0, 0, 630, 631, 1, 0, 0, 0, 631, 131, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 633, 634, 6, 66, -1, 0, 634, 642, 3, 142, 71, 0, 635, 642, 3, 46, 23, 0, 636, 642, 3, 134, 67, 0, 637, 638, 5, 100, 0, 0, 638, 639, 3, 122, 61, 0, 639, 640, 5, 101, 0, 0, 640, 642, 1, 0, 0, 0, 641, 633, 1, 0, 0, 0, 641, 635, 1, 0, 0, 0, 641, 636, 1, 0, 0, 0, 641, 637, 1, 0, 0, 0, 642, 648, 1, 0, 0, 0, 643, 644, 10, 1, 0, 0, 644, 645, 5, 61, 0, 0, 645, 647, 3, 10, 5, 0, 646, 643, 1, 0, 0, 0, 647, 650, 1, 0, 0, 0, 648, 646, 1, 0, 0, 0, 648, 649, 1, 0, 0, 0, 649, 133, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 651, 652, 3, 136, 68, 0, 652, 666, 5, 100, 0, 0, 653, 667, 5, 90, 0, 0, 654, 659, 3, 122, 61, 0, 655, 656, 5, 63, 0, 0, 656, 658, 3, 122, 61, 0, 657, 655, 1, 0, 0, 0, 658, 661, 1, 0, 0, 0, 659, 657, 1, 0, 0, 0, 659, 660, 1, 0, 0, 0, 660, 664, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 662, 663, 5, 63, 0, 0, 663, 665, 3, 138, 69, 0, 664, 662, 1, 0, 0, 0, 664, 665, 1, 0, 0, 0, 665, 667, 1, 0, 0, 0, 666, 653, 1, 0, 0, 0, 666, 654, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 669, 5, 101, 0, 0, 669, 135, 1, 0, 0, 0, 670, 671, 3, 60, 30, 0, 671, 137, 1, 0, 0, 0, 672, 673, 5, 93, 0, 0, 673, 678, 3, 140, 70, 0, 674, 675, 5, 63, 0, 0, 675, 677, 3, 140, 70, 0, 676, 674, 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, 682, 5, 94, 0, 0, 682, 139, 1, 0, 0, 0, 683, 684, 3, 152, 76, 0, 684, 685, 5, 62, 0, 0, 685, 686, 3, 142, 71, 0, 686, 141, 1, 0, 0, 0, 687, 730, 5, 73, 0, 0, 688, 689, 3, 150, 75, 0, 689, 690, 5, 102, 0, 0, 690, 730, 1, 0, 0, 0, 691, 730, 3, 148, 74, 0, 692, 730, 3, 150, 75, 0, 693, 730, 3, 144, 72, 0, 694, 730, 3, 56, 28, 0, 695, 730, 3, 152, 76, 0, 696, 697, 5, 98, 0, 0, 697, 702, 3, 146, 73, 0, 698, 699, 5, 63, 0, 0, 699, 701, 3, 146, 73, 0, 700, 698, 1, 0, 0, 0, 701, 704, 1, 0, 0, 0, 702, 700, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 705, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 705, 706, 5, 99, 0, 0, 706, 730, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 713, 3, 144, 72, 0, 709, 710, 5, 63, 0, 0, 710, 712, 3, 144, 72, 0, 711, 709, 1, 0, 0, 0, 712, 715, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 716, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 716, 717, 5, 99, 0, 0, 717, 730, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 724, 3, 152, 76, 0, 720, 721, 5, 63, 0, 0, 721, 723, 3, 152, 76, 0, 722, 720, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 727, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 728, 5, 99, 0, 0, 728, 730, 1, 0, 0, 0, 729, 687, 1, 0, 0, 0, 729, 688, 1, 0, 0, 0, 729, 691, 1, 0, 0, 0, 729, 692, 1, 0, 0, 0, 729, 693, 1, 0, 0, 0, 729, 694, 1, 0, 0, 0, 729, 695, 1, 0, 0, 0, 729, 696, 1, 0, 0, 0, 729, 707, 1, 0, 0, 0, 729, 718, 1, 0, 0, 0, 730, 143, 1, 0, 0, 0, 731, 732, 7, 6, 0, 0, 732, 145, 1, 0, 0, 0, 733, 736, 3, 148, 74, 0, 734, 736, 3, 150, 75, 0, 735, 733, 1, 0, 0, 0, 735, 734, 1, 0, 0, 0, 736, 147, 1, 0, 0, 0, 737, 739, 7, 4, 0, 0, 738, 737, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 740, 1, 0, 0, 0, 740, 741, 5, 55, 0, 0, 741, 149, 1, 0, 0, 0, 742, 744, 7, 4, 0, 0, 743, 742, 1, 0, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 746, 5, 54, 0, 0, 746, 151, 1, 0, 0, 0, 747, 748, 5, 53, 0, 0, 748, 153, 1, 0, 0, 0, 749, 750, 7, 7, 0, 0, 750, 155, 1, 0, 0, 0, 751, 752, 7, 8, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 3, 158, 79, 0, 754, 755, 3, 160, 80, 0, 755, 157, 1, 0, 0, 0, 756, 757, 3, 28, 14, 0, 757, 159, 1, 0, 0, 0, 758, 759, 5, 75, 0, 0, 759, 764, 3, 162, 81, 0, 760, 761, 5, 63, 0, 0, 761, 763, 3, 162, 81, 0, 762, 760, 1, 0, 0, 0, 763, 766, 1, 0, 0, 0, 764, 762, 1, 0, 0, 0, 764, 765, 1, 0, 0, 0, 765, 161, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 767, 768, 3, 128, 64, 0, 768, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 538, 550, 559, 567, 572, 580, 582, 587, 594, 599, 604, 614, 620, 628, 630, 641, 648, 659, 664, 666, 678, 702, 713, 724, 729, 735, 738, 743, 764] \ No newline at end of file +[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 537, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 803, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 542, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 62, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 58, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 98, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 74, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 79, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 62, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 58, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 74, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 59, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 74, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 132, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 62, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 99, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 100, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 74, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 79, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 536, 5, 7, 0, 0, 533, 534, 3, 46, 23, 0, 534, 535, 5, 58, 0, 0, 535, 537, 1, 0, 0, 0, 536, 533, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 3, 132, 66, 0, 539, 540, 5, 79, 0, 0, 540, 541, 3, 60, 30, 0, 541, 119, 1, 0, 0, 0, 542, 543, 5, 18, 0, 0, 543, 544, 3, 148, 74, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 71, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 71, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 67, 0, 0, 555, 556, 5, 99, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 62, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 100, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 68, 0, 0, 568, 570, 5, 71, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 72, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 75, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 71, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 70, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 71, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 77, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 60, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 61, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 99, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 100, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 60, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 99, 0, 0, 655, 669, 5, 89, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 62, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 62, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 100, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 92, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 62, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 93, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 61, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 72, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 101, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 97, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 62, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 97, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 62, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 97, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 62, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 98, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 114, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 74, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 62, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 536, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ 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 581136657cec6..6f3c676e44abc 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 @@ -4505,7 +4505,7 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { { setState(542); match(DEV_SAMPLE); - setState(541); + setState(543); ((SampleCommandContext)_localctx).probability = decimalValue(); } } @@ -4721,7 +4721,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(572); + setState(574); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { case 1: @@ -4730,9 +4730,9 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(544); + setState(546); match(NOT); - setState(545); + setState(547); booleanExpression(8); } break; @@ -4741,7 +4741,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(548); valueExpression(); } break; @@ -4750,7 +4750,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(547); + setState(549); regexBooleanExpression(); } break; @@ -4759,41 +4759,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); - valueExpression(); setState(550); + valueExpression(); + setState(552); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(549); + setState(551); match(NOT); } } - setState(552); + setState(554); match(IN); - setState(553); + setState(555); match(LP); - setState(554); + setState(556); valueExpression(); - setState(559); + setState(561); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(555); + setState(557); match(COMMA); - setState(556); + setState(558); valueExpression(); } } - setState(561); + setState(563); _errHandler.sync(this); _la = _input.LA(1); } - setState(562); + setState(564); match(RP); } break; @@ -4802,21 +4802,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(564); + setState(566); valueExpression(); - setState(565); - match(IS); setState(567); + match(IS); + setState(569); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(566); + setState(568); match(NOT); } } - setState(569); + setState(571); match(NULL); } break; @@ -4825,13 +4825,13 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(571); + setState(573); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(582); + setState(584); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -4839,7 +4839,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(580); + setState(582); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: @@ -4847,11 +4847,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(574); + setState(576); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(575); + setState(577); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(576); + setState(578); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4860,18 +4860,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(577); + setState(579); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(578); + setState(580); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(579); + setState(581); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(584); + setState(586); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,47,_ctx); } @@ -4926,48 +4926,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(599); + setState(601); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(585); - valueExpression(); setState(587); + valueExpression(); + setState(589); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(586); + setState(588); match(NOT); } } - setState(589); + setState(591); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(590); + setState(592); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(592); - valueExpression(); setState(594); + valueExpression(); + setState(596); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(593); + setState(595); match(NOT); } } - setState(596); + setState(598); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(597); + setState(599); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5027,23 +5027,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(601); + setState(603); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(604); + setState(606); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(602); + setState(604); match(CAST_OP); - setState(603); + setState(605); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(606); + setState(608); match(COLON); - setState(607); + setState(609); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5127,14 +5127,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(614); + setState(616); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(609); + setState(611); operatorExpression(0); } break; @@ -5142,11 +5142,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(610); + setState(612); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(611); + setState(613); comparisonOperator(); - setState(612); + setState(614); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5271,7 +5271,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(620); + setState(622); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { case 1: @@ -5280,7 +5280,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _ctx = _localctx; _prevctx = _localctx; - setState(617); + setState(619); primaryExpression(0); } break; @@ -5289,7 +5289,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(618); + setState(620); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5300,13 +5300,13 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(619); + setState(621); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(630); + setState(632); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -5314,7 +5314,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(628); + setState(630); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { case 1: @@ -5322,9 +5322,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(622); + setState(624); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(623); + setState(625); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 89)) & ~0x3f) == 0 && ((1L << (_la - 89)) & 7L) != 0)) ) { @@ -5335,7 +5335,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(624); + setState(626); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5344,9 +5344,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(625); + setState(627); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(626); + setState(628); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5357,14 +5357,14 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(627); + setState(629); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(632); + setState(634); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,55,_ctx); } @@ -5522,7 +5522,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(641); + setState(643); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { case 1: @@ -5531,7 +5531,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(634); + setState(636); constant(); } break; @@ -5540,7 +5540,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(635); + setState(637); qualifiedName(); } break; @@ -5549,7 +5549,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(638); functionExpression(); } break; @@ -5558,17 +5558,17 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(639); match(LP); - setState(638); + setState(640); booleanExpression(0); - setState(639); + setState(641); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(648); + setState(650); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -5579,16 +5579,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(643); + setState(645); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(644); + setState(646); match(CAST_OP); - setState(645); + setState(647); dataType(); } } } - setState(650); + setState(652); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,57,_ctx); } @@ -5654,16 +5654,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(651); + setState(653); functionName(); - setState(652); + setState(654); match(LP); - setState(666); + setState(668); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(653); + setState(655); match(ASTERISK); } break; @@ -5686,34 +5686,34 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(654); + setState(656); booleanExpression(0); - setState(659); + setState(661); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(655); + setState(657); match(COMMA); - setState(656); + setState(658); booleanExpression(0); } } } - setState(661); + setState(663); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } - setState(664); + setState(666); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(662); + setState(664); match(COMMA); - setState(663); + setState(665); mapExpression(); } } @@ -5726,7 +5726,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(668); + setState(670); match(RP); } } @@ -5772,7 +5772,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(670); + setState(672); identifierOrParameter(); } } @@ -5828,27 +5828,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(674); match(LEFT_BRACES); - setState(673); + setState(675); entryExpression(); - setState(678); + setState(680); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(674); + setState(676); match(COMMA); - setState(675); + setState(677); entryExpression(); } } - setState(680); + setState(682); _errHandler.sync(this); _la = _input.LA(1); } - setState(681); + setState(683); match(RIGHT_BRACES); } } @@ -5900,11 +5900,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(683); + setState(685); ((EntryExpressionContext)_localctx).key = string(); - setState(684); + setState(686); match(COLON); - setState(685); + setState(687); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6175,14 +6175,14 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(729); + setState(731); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(687); + setState(689); match(NULL); } break; @@ -6190,9 +6190,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(688); + setState(690); integerValue(); - setState(689); + setState(691); match(UNQUOTED_IDENTIFIER); } break; @@ -6200,7 +6200,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(691); + setState(693); decimalValue(); } break; @@ -6208,7 +6208,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(692); + setState(694); integerValue(); } break; @@ -6216,7 +6216,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(693); + setState(695); booleanValue(); } break; @@ -6224,7 +6224,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(694); + setState(696); parameter(); } break; @@ -6232,7 +6232,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(695); + setState(697); string(); } break; @@ -6240,27 +6240,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(696); + setState(698); match(OPENING_BRACKET); - setState(697); + setState(699); numericValue(); - setState(702); + setState(704); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(698); + setState(700); match(COMMA); - setState(699); + setState(701); numericValue(); } } - setState(704); + setState(706); _errHandler.sync(this); _la = _input.LA(1); } - setState(705); + setState(707); match(CLOSING_BRACKET); } break; @@ -6268,27 +6268,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(707); + setState(709); match(OPENING_BRACKET); - setState(708); + setState(710); booleanValue(); - setState(713); + setState(715); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(709); + setState(711); match(COMMA); - setState(710); + setState(712); booleanValue(); } } - setState(715); + setState(717); _errHandler.sync(this); _la = _input.LA(1); } - setState(716); + setState(718); match(CLOSING_BRACKET); } break; @@ -6296,27 +6296,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(718); + setState(720); match(OPENING_BRACKET); - setState(719); + setState(721); string(); - setState(724); + setState(726); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(720); + setState(722); match(COMMA); - setState(721); + setState(723); string(); } } - setState(726); + setState(728); _errHandler.sync(this); _la = _input.LA(1); } - setState(727); + setState(729); match(CLOSING_BRACKET); } break; @@ -6364,7 +6364,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(731); + setState(733); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6419,20 +6419,20 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(735); + setState(737); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(733); + setState(735); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(734); + setState(736); integerValue(); } break; @@ -6481,12 +6481,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(738); + setState(740); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(737); + setState(739); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6499,7 +6499,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(740); + setState(742); match(DECIMAL_LITERAL); } } @@ -6546,12 +6546,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(743); + setState(745); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(742); + setState(744); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6564,7 +6564,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(745); + setState(747); match(INTEGER_LITERAL); } } @@ -6608,7 +6608,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(747); + setState(749); match(QUOTED_STRING); } } @@ -6658,7 +6658,7 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(751); _la = _input.LA(1); if ( !(((((_la - 80)) & ~0x3f) == 0 && ((1L << (_la - 80)) & 125L) != 0)) ) { _errHandler.recoverInline(this); @@ -6721,7 +6721,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(753); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6732,11 +6732,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(752); + setState(754); match(JOIN); - setState(753); + setState(755); joinTarget(); - setState(754); + setState(756); joinCondition(); } } @@ -6783,7 +6783,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(756); + setState(758); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6838,25 +6838,25 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(758); + setState(760); match(ON); - setState(759); + setState(761); joinPredicate(); - setState(764); + setState(766); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(760); + setState(762); match(COMMA); - setState(761); + setState(763); joinPredicate(); } } } - setState(766); + setState(768); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,69,_ctx); } @@ -6904,7 +6904,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(767); + setState(769); valueExpression(); } } @@ -7005,7 +7005,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0302\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u0304\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"+ @@ -7073,243 +7073,243 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "5\u01f0\b5\u000b5\f5\u01f1\u00016\u00016\u00016\u00016\u00017\u00017\u0001"+ "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ - ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0001"+ - ";\u0001;\u0003;\u021b\b;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0003=\u0227\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ - "=\u0005=\u022e\b=\n=\f=\u0231\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003"+ - "=\u0238\b=\u0001=\u0001=\u0001=\u0003=\u023d\b=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0245\b=\n=\f=\u0248\t=\u0001>\u0001>\u0003>\u024c"+ - "\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0253\b>\u0001>\u0001>\u0001"+ - ">\u0003>\u0258\b>\u0001?\u0001?\u0001?\u0003?\u025d\b?\u0001?\u0001?\u0001"+ - "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0267\b@\u0001A\u0001A\u0001"+ - "A\u0001A\u0003A\u026d\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ - "A\u0275\bA\nA\fA\u0278\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0003B\u0282\bB\u0001B\u0001B\u0001B\u0005B\u0287\bB\nB\fB\u028a"+ - "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0292\bC\nC\fC\u0295"+ - "\tC\u0001C\u0001C\u0003C\u0299\bC\u0003C\u029b\bC\u0001C\u0001C\u0001"+ - "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a5\bE\nE\fE\u02a8\tE\u0001"+ - "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02bd"+ - "\bG\nG\fG\u02c0\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c8"+ - "\bG\nG\fG\u02cb\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d3"+ - "\bG\nG\fG\u02d6\tG\u0001G\u0001G\u0003G\u02da\bG\u0001H\u0001H\u0001I"+ - "\u0001I\u0003I\u02e0\bI\u0001J\u0003J\u02e3\bJ\u0001J\u0001J\u0001K\u0003"+ - "K\u02e8\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ - "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u02fb"+ - "\bP\nP\fP\u02fe\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ - "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ - "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ - "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ - "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055ll\u0001\u0000fg\u0002\u0000"+ - "::@@\u0002\u0000CCFF\u0001\u0000XY\u0001\u0000Z\\\u0002\u0000BBOO\u0002"+ - "\u0000QQSW\u0002\u0000\u0016\u0016\u0018\u0019\u0321\u0000\u00a4\u0001"+ - "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ - "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ - "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ - "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ - "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ - "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ - "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ - "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ - "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ - "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ - "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ - "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ - "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ - "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ - ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ - "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ - "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0191\u0001\u0000\u0000\u0000"+ - "L\u0195\u0001\u0000\u0000\u0000N\u019b\u0001\u0000\u0000\u0000P\u019f"+ - "\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000\u0000\u0000T\u01aa\u0001\u0000"+ - "\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000X\u01b1\u0001\u0000\u0000\u0000"+ - "Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8\u0001\u0000\u0000\u0000^\u01cc"+ - "\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000\u0000\u0000b\u01d5\u0001\u0000"+ - "\u0000\u0000d\u01db\u0001\u0000\u0000\u0000f\u01e8\u0001\u0000\u0000\u0000"+ - "h\u01eb\u0001\u0000\u0000\u0000j\u01ef\u0001\u0000\u0000\u0000l\u01f3"+ - "\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000\u0000\u0000p\u0208\u0001\u0000"+ - "\u0000\u0000r\u020a\u0001\u0000\u0000\u0000t\u020c\u0001\u0000\u0000\u0000"+ - "v\u0214\u0001\u0000\u0000\u0000x\u021c\u0001\u0000\u0000\u0000z\u023c"+ - "\u0001\u0000\u0000\u0000|\u0257\u0001\u0000\u0000\u0000~\u0259\u0001\u0000"+ - "\u0000\u0000\u0080\u0266\u0001\u0000\u0000\u0000\u0082\u026c\u0001\u0000"+ - "\u0000\u0000\u0084\u0281\u0001\u0000\u0000\u0000\u0086\u028b\u0001\u0000"+ - "\u0000\u0000\u0088\u029e\u0001\u0000\u0000\u0000\u008a\u02a0\u0001\u0000"+ - "\u0000\u0000\u008c\u02ab\u0001\u0000\u0000\u0000\u008e\u02d9\u0001\u0000"+ - "\u0000\u0000\u0090\u02db\u0001\u0000\u0000\u0000\u0092\u02df\u0001\u0000"+ - "\u0000\u0000\u0094\u02e2\u0001\u0000\u0000\u0000\u0096\u02e7\u0001\u0000"+ - "\u0000\u0000\u0098\u02eb\u0001\u0000\u0000\u0000\u009a\u02ed\u0001\u0000"+ - "\u0000\u0000\u009c\u02ef\u0001\u0000\u0000\u0000\u009e\u02f4\u0001\u0000"+ - "\u0000\u0000\u00a0\u02f6\u0001\u0000\u0000\u0000\u00a2\u02ff\u0001\u0000"+ - "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ - "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ - "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ - "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ - "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ - "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ - "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ - "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ - "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ - "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ - "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ - "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ - "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ - "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ - "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ - "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ - "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ - "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ - "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ - "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ - "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ - "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ - "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ - "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ - "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ - "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ - "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ - "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ - "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ - "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ - "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ - "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005?\u0000"+ - "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ - "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ - "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ - "\u00eb\u0005;\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ - "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ - "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ - "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005?\u0000"+ - "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ - "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ - "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ - "\u00fa\u0005;\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ - "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ - "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ - "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ - "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ - "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005?\u0000\u0000"+ - "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ - "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ - "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ - "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ - "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ - "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ - "\u0110\u0005>\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ - "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ - "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ - "\"\u0011\u0000\u0115\u0116\u0005=\u0000\u0000\u0116\u0118\u0003 \u0010"+ - "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ - "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ - "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ - "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ - "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ - "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ - "\u0122\u0005k\u0000\u0000\u0122\u0127\u0005l\u0000\u0000\u0123\u0124\u0005"+ - "?\u0000\u0000\u0124\u0126\u0005l\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ - "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ - "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ - "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ - "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ - "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ - "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ - "\u0000\u0000\u0000\u0131\u0132\u0005<\u0000\u0000\u0132\u0134\u0003\u000e"+ - "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ - "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ - "\u0136\u0137\u0005?\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ - "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ - "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ - "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ - "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ - "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ - "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ - "\u0144\u0005A\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ - "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ - "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ - "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ - "\u0000\u014b\u014c\u0005A\u0000\u0000\u014c\u014e\u00036\u001b\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"+ - "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ - "\u00030\u0018\u0000\u0153\u0154\u0005?\u0000\u0000\u0154\u0156\u00030"+ - "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ - "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ - "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ - "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ - "\u015c\u0160\u0005\u0081\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ - "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ - "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ - "\u0000\u0000\u0000\u0161\u0164\u0005M\u0000\u0000\u0162\u0164\u0005`\u0000"+ - "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ - "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005_\u0000\u0000\u0166"+ - "\u0168\u0005a\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ - "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ - "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ - "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ - "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ - "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ - "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ - "\u0003B!\u0000\u0173\u0174\u0005?\u0000\u0000\u0174\u0176\u0003B!\u0000"+ - "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ - "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ - "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ - "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ - "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ - "\u0000\u0000\u0000\u017e\u017f\u0005J\u0000\u0000\u017f\u0181\u0007\u0003"+ - "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ - "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ - "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ - "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ - "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ - "\u0000\u018a\u018b\u0005?\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ - "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ - "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ - "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ - "\u00030\u0018\u0000\u0192\u0193\u00059\u0000\u0000\u0193\u0194\u00030"+ - "\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005\b\u0000"+ - "\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098L\u0000\u0198"+ - "\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000\u0199\u019a\u0001"+ - "\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b\u019c\u0005\n"+ - "\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e\u0003\u0098L\u0000"+ - "\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b\u0000\u0000\u01a0"+ - "\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000\u01a2\u01a7\u0003"+ - "T*\u0000\u01a3\u01a4\u0005?\u0000\u0000\u01a4\u01a6\u0003T*\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"+ - "S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000\u0000\u01aa\u01ab"+ - "\u00034\u001a\u0000\u01ab\u01ac\u0005;\u0000\u0000\u01ac\u01ad\u0003\u008e"+ - "G\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005\u0006\u0000\u0000"+ - "\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000\u0000\u01b1\u01b2"+ - "\u0005b\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000\u01b3\u01b4\u0005"+ - "c\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005!\u0000"+ - "\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000\u0000\u0000"+ - "\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000\u0000\u01ba"+ - "\u01bb\u0005K\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc\u01ba\u0001"+ - "\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd\u01c7\u0001"+ - "\u0000\u0000\u0000\u01be\u01bf\u0005P\u0000\u0000\u01bf\u01c4\u0003^/"+ - "\u0000\u01c0\u01c1\u0005?\u0000\u0000\u01c1\u01c3\u0003^/\u0000\u01c2"+ - "\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000\u01c4"+ - "\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000\u01c5"+ - "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7"+ - "\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000\u01c8"+ - "]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb\u0005"+ - ";\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001\u0000"+ - "\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001\u0000"+ - "\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000\u0000"+ - "\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e\u0000"+ - "\u01d2\u01d3\u0005K\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4a"+ - "\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ - "\u0003*\u0015\u0000\u01d7\u01d8\u0005<\u0000\u0000\u01d8\u01da\u0003\u000e"+ + ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0003"+ + ";\u0219\b;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001<\u0001<\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ + "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ + "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ + ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ + ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ + "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ + "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ + "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ + "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ + "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ + "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ + "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ + "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ + "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ + "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ + "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ + "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ + "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ + "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ + "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ + "5kk\u0001\u0000ef\u0002\u000099??\u0002\u0000BBEE\u0001\u0000WX\u0001"+ + "\u0000Y[\u0002\u0000AANN\u0002\u0000PPRV\u0002\u0000\u0016\u0016\u0018"+ + "\u0019\u0323\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ + "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ + "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ + "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ + "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ + "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ + "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ + "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ + " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ + "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ + "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ + ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ + "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ + "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ + "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ + "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ + "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ + "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ + "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ + "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ + "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ + "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ + "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ + "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ + "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ + "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ + "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021e"+ + "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ + "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ + "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ + "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ + "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ + "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ + "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ + "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ + "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ + "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ + "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ + "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ + "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ + "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ + "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ + "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ + "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ + "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ + "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ + "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ + "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ + "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ + "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ + "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ + "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ + " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ + "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ + "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ + "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ + "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ + "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ + "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ + "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ + "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ + "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ + "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ + "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ + "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ + "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ + "\u0000\u00e2\u00e3\u0005>\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ + "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ + "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ + "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005:\u0000\u0000\u00eb\u00ed"+ + "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ + "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ + "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ + "\n\u0000\u00f1\u00f2\u0005>\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ + "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ + "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ + "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005:\u0000\u0000\u00fa\u00fc"+ + "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ + "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ + "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ + "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ + "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ + "\u0104\u0105\u0005>\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ + "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ + "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ + "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ + "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ + "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ + "\u0003\u001e\u000f\u0000\u010f\u0110\u0005=\u0000\u0000\u0110\u0112\u0001"+ + "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ + "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ + "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005<\u0000"+ + "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ + "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ + "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ + "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ + "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ + "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ + "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005j\u0000\u0000\u0122\u0127\u0005"+ + "k\u0000\u0000\u0123\u0124\u0005>\u0000\u0000\u0124\u0126\u0005k\u0000"+ + "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ + "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ + "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ + "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ + "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ + "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ + "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ + ";\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ + "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ + "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005>\u0000\u0000\u0137"+ + "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ + "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ + "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ + "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ + "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ + "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ + "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005@\u0000\u0000\u0144\u0146\u0003"+ + "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ + "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ + "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ + "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005@\u0000\u0000\u014c"+ + "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ + "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005>\u0000"+ + "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ + "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ + "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ + "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ + "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160"+ + "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ + "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ + "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000"+ + "\u0162\u0164\u0005_\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ + "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ + "\u0005^\u0000\u0000\u0166\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000"+ + "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ + "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ + "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ + "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ + "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ + "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ + "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174"+ + "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ + "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ + "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ + "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ + "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ + "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f"+ + "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ + "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ + "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ + "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ + "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ + "\u018e\u0003J%\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003"+ + "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ + "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ + "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ + "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193"+ + "\u0194\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005"+ + "\b\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098"+ + "L\u0000\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000"+ + "\u0199\u019a\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b"+ + "\u019c\u0005\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e"+ + "\u0003\u0098L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b"+ + "\u0000\u0000\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000"+ + "\u01a2\u01a7\u0003T*\u0000\u01a3\u01a4\u0005>\u0000\u0000\u01a4\u01a6"+ + "\u0003T*\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\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000"+ + "\u0000\u01aa\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005:\u0000\u0000\u01ac"+ + "\u01ad\u0003\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005"+ + "\u0006\u0000\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000"+ + "\u0000\u01b1\u01b2\u0005a\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000"+ + "\u01b3\u01b4\u0005b\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6"+ + "\u0005!\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000"+ + "\u0000\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000"+ + "\u0000\u01ba\u01bb\u0005J\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc"+ + "\u01ba\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd"+ + "\u01c7\u0001\u0000\u0000\u0000\u01be\u01bf\u0005O\u0000\u0000\u01bf\u01c4"+ + "\u0003^/\u0000\u01c0\u01c1\u0005>\u0000\u0000\u01c1\u01c3\u0003^/\u0000"+ + "\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000"+ + "\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000"+ + "\u01c5\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000"+ + "\u01c7\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000"+ + "\u01c8]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb"+ + "\u0005:\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001"+ + "\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001"+ + "\u0000\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000"+ + "\u0000\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e"+ + "\u0000\u01d2\u01d3\u0005J\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4"+ + "a\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ + "\u0003*\u0015\u0000\u01d7\u01d8\u0005;\u0000\u0000\u01d8\u01da\u0003\u000e"+ "\u0007\u0000\u01d9\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001\u0000"+ "\u0000\u0000\u01dac\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0004\u0000"+ "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005J\u0000\u0000\u01de"+ @@ -7342,143 +7342,144 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "\u020f\u0005J\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ "\u0005O\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ "\u0000\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213u\u0001\u0000\u0000"+ - "\u0000\u0214\u0215\u0005\u0007\u0000\u0000\u0215\u0216\u0003\u0084B\u0000"+ - "\u0216\u0217\u0005P\u0000\u0000\u0217\u021a\u0003<\u001e\u0000\u0218\u0219"+ - "\u00059\u0000\u0000\u0219\u021b\u0003.\u0017\u0000\u021a\u0218\u0001\u0000"+ - "\u0000\u0000\u021a\u021b\u0001\u0000\u0000\u0000\u021bw\u0001\u0000\u0000"+ - "\u0000\u021c\u021d\u0005\u0012\u0000\u0000\u021d\u021e\u0003\u0094J\u0000"+ - "\u021ey\u0001\u0000\u0000\u0000\u021f\u0220\u0006=\uffff\uffff\u0000\u0220"+ - "\u0221\u0005H\u0000\u0000\u0221\u023d\u0003z=\b\u0222\u023d\u0003\u0080"+ - "@\u0000\u0223\u023d\u0003|>\u0000\u0224\u0226\u0003\u0080@\u0000\u0225"+ - "\u0227\u0005H\u0000\u0000\u0226\u0225\u0001\u0000\u0000\u0000\u0226\u0227"+ - "\u0001\u0000\u0000\u0000\u0227\u0228\u0001\u0000\u0000\u0000\u0228\u0229"+ - "\u0005D\u0000\u0000\u0229\u022a\u0005d\u0000\u0000\u022a\u022f\u0003\u0080"+ - "@\u0000\u022b\u022c\u0005?\u0000\u0000\u022c\u022e\u0003\u0080@\u0000"+ - "\u022d\u022b\u0001\u0000\u0000\u0000\u022e\u0231\u0001\u0000\u0000\u0000"+ - "\u022f\u022d\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000\u0000\u0000"+ - "\u0230\u0232\u0001\u0000\u0000\u0000\u0231\u022f\u0001\u0000\u0000\u0000"+ - "\u0232\u0233\u0005e\u0000\u0000\u0233\u023d\u0001\u0000\u0000\u0000\u0234"+ - "\u0235\u0003\u0080@\u0000\u0235\u0237\u0005E\u0000\u0000\u0236\u0238\u0005"+ - "H\u0000\u0000\u0237\u0236\u0001\u0000\u0000\u0000\u0237\u0238\u0001\u0000"+ - "\u0000\u0000\u0238\u0239\u0001\u0000\u0000\u0000\u0239\u023a\u0005I\u0000"+ - "\u0000\u023a\u023d\u0001\u0000\u0000\u0000\u023b\u023d\u0003~?\u0000\u023c"+ - "\u021f\u0001\u0000\u0000\u0000\u023c\u0222\u0001\u0000\u0000\u0000\u023c"+ - "\u0223\u0001\u0000\u0000\u0000\u023c\u0224\u0001\u0000\u0000\u0000\u023c"+ - "\u0234\u0001\u0000\u0000\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d"+ - "\u0246\u0001\u0000\u0000\u0000\u023e\u023f\n\u0005\u0000\u0000\u023f\u0240"+ - "\u00058\u0000\u0000\u0240\u0245\u0003z=\u0006\u0241\u0242\n\u0004\u0000"+ - "\u0000\u0242\u0243\u0005L\u0000\u0000\u0243\u0245\u0003z=\u0005\u0244"+ - "\u023e\u0001\u0000\u0000\u0000\u0244\u0241\u0001\u0000\u0000\u0000\u0245"+ - "\u0248\u0001\u0000\u0000\u0000\u0246\u0244\u0001\u0000\u0000\u0000\u0246"+ - "\u0247\u0001\u0000\u0000\u0000\u0247{\u0001\u0000\u0000\u0000\u0248\u0246"+ - "\u0001\u0000\u0000\u0000\u0249\u024b\u0003\u0080@\u0000\u024a\u024c\u0005"+ - "H\u0000\u0000\u024b\u024a\u0001\u0000\u0000\u0000\u024b\u024c\u0001\u0000"+ - "\u0000\u0000\u024c\u024d\u0001\u0000\u0000\u0000\u024d\u024e\u0005G\u0000"+ - "\u0000\u024e\u024f\u0003\u0098L\u0000\u024f\u0258\u0001\u0000\u0000\u0000"+ - "\u0250\u0252\u0003\u0080@\u0000\u0251\u0253\u0005H\u0000\u0000\u0252\u0251"+ - "\u0001\u0000\u0000\u0000\u0252\u0253\u0001\u0000\u0000\u0000\u0253\u0254"+ - "\u0001\u0000\u0000\u0000\u0254\u0255\u0005N\u0000\u0000\u0255\u0256\u0003"+ - "\u0098L\u0000\u0256\u0258\u0001\u0000\u0000\u0000\u0257\u0249\u0001\u0000"+ - "\u0000\u0000\u0257\u0250\u0001\u0000\u0000\u0000\u0258}\u0001\u0000\u0000"+ - "\u0000\u0259\u025c\u0003.\u0017\u0000\u025a\u025b\u0005=\u0000\u0000\u025b"+ - "\u025d\u0003\n\u0005\u0000\u025c\u025a\u0001\u0000\u0000\u0000\u025c\u025d"+ - "\u0001\u0000\u0000\u0000\u025d\u025e\u0001\u0000\u0000\u0000\u025e\u025f"+ - "\u0005>\u0000\u0000\u025f\u0260\u0003\u008eG\u0000\u0260\u007f\u0001\u0000"+ - "\u0000\u0000\u0261\u0267\u0003\u0082A\u0000\u0262\u0263\u0003\u0082A\u0000"+ - "\u0263\u0264\u0003\u009aM\u0000\u0264\u0265\u0003\u0082A\u0000\u0265\u0267"+ - "\u0001\u0000\u0000\u0000\u0266\u0261\u0001\u0000\u0000\u0000\u0266\u0262"+ - "\u0001\u0000\u0000\u0000\u0267\u0081\u0001\u0000\u0000\u0000\u0268\u0269"+ - "\u0006A\uffff\uffff\u0000\u0269\u026d\u0003\u0084B\u0000\u026a\u026b\u0007"+ - "\u0004\u0000\u0000\u026b\u026d\u0003\u0082A\u0003\u026c\u0268\u0001\u0000"+ - "\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026d\u0276\u0001\u0000"+ - "\u0000\u0000\u026e\u026f\n\u0002\u0000\u0000\u026f\u0270\u0007\u0005\u0000"+ - "\u0000\u0270\u0275\u0003\u0082A\u0003\u0271\u0272\n\u0001\u0000\u0000"+ - "\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0002\u0274"+ - "\u026e\u0001\u0000\u0000\u0000\u0274\u0271\u0001\u0000\u0000\u0000\u0275"+ - "\u0278\u0001\u0000\u0000\u0000\u0276\u0274\u0001\u0000\u0000\u0000\u0276"+ - "\u0277\u0001\u0000\u0000\u0000\u0277\u0083\u0001\u0000\u0000\u0000\u0278"+ - "\u0276\u0001\u0000\u0000\u0000\u0279\u027a\u0006B\uffff\uffff\u0000\u027a"+ - "\u0282\u0003\u008eG\u0000\u027b\u0282\u0003.\u0017\u0000\u027c\u0282\u0003"+ - "\u0086C\u0000\u027d\u027e\u0005d\u0000\u0000\u027e\u027f\u0003z=\u0000"+ - "\u027f\u0280\u0005e\u0000\u0000\u0280\u0282\u0001\u0000\u0000\u0000\u0281"+ - "\u0279\u0001\u0000\u0000\u0000\u0281\u027b\u0001\u0000\u0000\u0000\u0281"+ - "\u027c\u0001\u0000\u0000\u0000\u0281\u027d\u0001\u0000\u0000\u0000\u0282"+ - "\u0288\u0001\u0000\u0000\u0000\u0283\u0284\n\u0001\u0000\u0000\u0284\u0285"+ - "\u0005=\u0000\u0000\u0285\u0287\u0003\n\u0005\u0000\u0286\u0283\u0001"+ - "\u0000\u0000\u0000\u0287\u028a\u0001\u0000\u0000\u0000\u0288\u0286\u0001"+ - "\u0000\u0000\u0000\u0288\u0289\u0001\u0000\u0000\u0000\u0289\u0085\u0001"+ - "\u0000\u0000\u0000\u028a\u0288\u0001\u0000\u0000\u0000\u028b\u028c\u0003"+ - "\u0088D\u0000\u028c\u029a\u0005d\u0000\u0000\u028d\u029b\u0005Z\u0000"+ - "\u0000\u028e\u0293\u0003z=\u0000\u028f\u0290\u0005?\u0000\u0000\u0290"+ - "\u0292\u0003z=\u0000\u0291\u028f\u0001\u0000\u0000\u0000\u0292\u0295\u0001"+ - "\u0000\u0000\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0293\u0294\u0001"+ - "\u0000\u0000\u0000\u0294\u0298\u0001\u0000\u0000\u0000\u0295\u0293\u0001"+ - "\u0000\u0000\u0000\u0296\u0297\u0005?\u0000\u0000\u0297\u0299\u0003\u008a"+ - "E\u0000\u0298\u0296\u0001\u0000\u0000\u0000\u0298\u0299\u0001\u0000\u0000"+ - "\u0000\u0299\u029b\u0001\u0000\u0000\u0000\u029a\u028d\u0001\u0000\u0000"+ - "\u0000\u029a\u028e\u0001\u0000\u0000\u0000\u029a\u029b\u0001\u0000\u0000"+ - "\u0000\u029b\u029c\u0001\u0000\u0000\u0000\u029c\u029d\u0005e\u0000\u0000"+ - "\u029d\u0087\u0001\u0000\u0000\u0000\u029e\u029f\u0003<\u001e\u0000\u029f"+ - "\u0089\u0001\u0000\u0000\u0000\u02a0\u02a1\u0005]\u0000\u0000\u02a1\u02a6"+ - "\u0003\u008cF\u0000\u02a2\u02a3\u0005?\u0000\u0000\u02a3\u02a5\u0003\u008c"+ - "F\u0000\u02a4\u02a2\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\u02aa\u0005^\u0000\u0000\u02aa\u008b\u0001\u0000\u0000\u0000"+ - "\u02ab\u02ac\u0003\u0098L\u0000\u02ac\u02ad\u0005>\u0000\u0000\u02ad\u02ae"+ - "\u0003\u008eG\u0000\u02ae\u008d\u0001\u0000\u0000\u0000\u02af\u02da\u0005"+ - "I\u0000\u0000\u02b0\u02b1\u0003\u0096K\u0000\u02b1\u02b2\u0005f\u0000"+ - "\u0000\u02b2\u02da\u0001\u0000\u0000\u0000\u02b3\u02da\u0003\u0094J\u0000"+ - "\u02b4\u02da\u0003\u0096K\u0000\u02b5\u02da\u0003\u0090H\u0000\u02b6\u02da"+ - "\u00038\u001c\u0000\u02b7\u02da\u0003\u0098L\u0000\u02b8\u02b9\u0005b"+ - "\u0000\u0000\u02b9\u02be\u0003\u0092I\u0000\u02ba\u02bb\u0005?\u0000\u0000"+ - "\u02bb\u02bd\u0003\u0092I\u0000\u02bc\u02ba\u0001\u0000\u0000\u0000\u02bd"+ - "\u02c0\u0001\u0000\u0000\u0000\u02be\u02bc\u0001\u0000\u0000\u0000\u02be"+ - "\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c1\u0001\u0000\u0000\u0000\u02c0"+ - "\u02be\u0001\u0000\u0000\u0000\u02c1\u02c2\u0005c\u0000\u0000\u02c2\u02da"+ - "\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000\u02c4\u02c9\u0003"+ - "\u0090H\u0000\u02c5\u02c6\u0005?\u0000\u0000\u02c6\u02c8\u0003\u0090H"+ - "\u0000\u02c7\u02c5\u0001\u0000\u0000\u0000\u02c8\u02cb\u0001\u0000\u0000"+ - "\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001\u0000\u0000"+ - "\u0000\u02ca\u02cc\u0001\u0000\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000"+ - "\u0000\u02cc\u02cd\u0005c\u0000\u0000\u02cd\u02da\u0001\u0000\u0000\u0000"+ - "\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02d4\u0003\u0098L\u0000\u02d0\u02d1"+ - "\u0005?\u0000\u0000\u02d1\u02d3\u0003\u0098L\u0000\u02d2\u02d0\u0001\u0000"+ - "\u0000\u0000\u02d3\u02d6\u0001\u0000\u0000\u0000\u02d4\u02d2\u0001\u0000"+ - "\u0000\u0000\u02d4\u02d5\u0001\u0000\u0000\u0000\u02d5\u02d7\u0001\u0000"+ - "\u0000\u0000\u02d6\u02d4\u0001\u0000\u0000\u0000\u02d7\u02d8\u0005c\u0000"+ - "\u0000\u02d8\u02da\u0001\u0000\u0000\u0000\u02d9\u02af\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b0\u0001\u0000\u0000\u0000\u02d9\u02b3\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b4\u0001\u0000\u0000\u0000\u02d9\u02b5\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b6\u0001\u0000\u0000\u0000\u02d9\u02b7\u0001\u0000\u0000"+ - "\u0000\u02d9\u02b8\u0001\u0000\u0000\u0000\u02d9\u02c3\u0001\u0000\u0000"+ - "\u0000\u02d9\u02ce\u0001\u0000\u0000\u0000\u02da\u008f\u0001\u0000\u0000"+ - "\u0000\u02db\u02dc\u0007\u0006\u0000\u0000\u02dc\u0091\u0001\u0000\u0000"+ - "\u0000\u02dd\u02e0\u0003\u0094J\u0000\u02de\u02e0\u0003\u0096K\u0000\u02df"+ - "\u02dd\u0001\u0000\u0000\u0000\u02df\u02de\u0001\u0000\u0000\u0000\u02e0"+ - "\u0093\u0001\u0000\u0000\u0000\u02e1\u02e3\u0007\u0004\u0000\u0000\u02e2"+ - "\u02e1\u0001\u0000\u0000\u0000\u02e2\u02e3\u0001\u0000\u0000\u0000\u02e3"+ - "\u02e4\u0001\u0000\u0000\u0000\u02e4\u02e5\u00057\u0000\u0000\u02e5\u0095"+ - "\u0001\u0000\u0000\u0000\u02e6\u02e8\u0007\u0004\u0000\u0000\u02e7\u02e6"+ - "\u0001\u0000\u0000\u0000\u02e7\u02e8\u0001\u0000\u0000\u0000\u02e8\u02e9"+ - "\u0001\u0000\u0000\u0000\u02e9\u02ea\u00056\u0000\u0000\u02ea\u0097\u0001"+ - "\u0000\u0000\u0000\u02eb\u02ec\u00055\u0000\u0000\u02ec\u0099\u0001\u0000"+ - "\u0000\u0000\u02ed\u02ee\u0007\u0007\u0000\u0000\u02ee\u009b\u0001\u0000"+ - "\u0000\u0000\u02ef\u02f0\u0007\b\u0000\u0000\u02f0\u02f1\u0005s\u0000"+ - "\u0000\u02f1\u02f2\u0003\u009eO\u0000\u02f2\u02f3\u0003\u00a0P\u0000\u02f3"+ - "\u009d\u0001\u0000\u0000\u0000\u02f4\u02f5\u0003\u001c\u000e\u0000\u02f5"+ - "\u009f\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005K\u0000\u0000\u02f7\u02fc"+ - "\u0003\u00a2Q\u0000\u02f8\u02f9\u0005?\u0000\u0000\u02f9\u02fb\u0003\u00a2"+ - "Q\u0000\u02fa\u02f8\u0001\u0000\u0000\u0000\u02fb\u02fe\u0001\u0000\u0000"+ - "\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fc\u02fd\u0001\u0000\u0000"+ - "\u0000\u02fd\u00a1\u0001\u0000\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000"+ - "\u0000\u02ff\u0300\u0003\u0080@\u0000\u0300\u00a3\u0001\u0000\u0000\u0000"+ - "F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117\u0119"+ - "\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167\u016c"+ - "\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9\u01df"+ - "\u01e6\u01f1\u01ff\u0208\u0212\u021a\u0226\u022f\u0237\u023c\u0244\u0246"+ - "\u024b\u0252\u0257\u025c\u0266\u026c\u0274\u0276\u0281\u0288\u0293\u0298"+ - "\u029a\u02a6\u02be\u02c9\u02d4\u02d9\u02df\u02e2\u02e7\u02fc"; + "\u0000\u0214\u0218\u0005\u0007\u0000\u0000\u0215\u0216\u0003.\u0017\u0000"+ + "\u0216\u0217\u0005:\u0000\u0000\u0217\u0219\u0001\u0000\u0000\u0000\u0218"+ + "\u0215\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219"+ + "\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0003\u0084B\u0000\u021b\u021c"+ + "\u0005O\u0000\u0000\u021c\u021d\u0003<\u001e\u0000\u021dw\u0001\u0000"+ + "\u0000\u0000\u021e\u021f\u0005\u0012\u0000\u0000\u021f\u0220\u0003\u0094"+ + "J\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222\u0006=\uffff\uffff"+ + "\u0000\u0222\u0223\u0005G\u0000\u0000\u0223\u023f\u0003z=\b\u0224\u023f"+ + "\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228\u0003\u0080"+ + "@\u0000\u0227\u0229\u0005G\u0000\u0000\u0228\u0227\u0001\u0000\u0000\u0000"+ + "\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000\u0000\u0000"+ + "\u022a\u022b\u0005C\u0000\u0000\u022b\u022c\u0005c\u0000\u0000\u022c\u0231"+ + "\u0003\u0080@\u0000\u022d\u022e\u0005>\u0000\u0000\u022e\u0230\u0003\u0080"+ + "@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001\u0000\u0000"+ + "\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001\u0000\u0000"+ + "\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001\u0000\u0000"+ + "\u0000\u0234\u0235\u0005d\u0000\u0000\u0235\u023f\u0001\u0000\u0000\u0000"+ + "\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005D\u0000\u0000\u0238\u023a"+ + "\u0005G\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239\u023a\u0001"+ + "\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0005"+ + "H\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f\u0003~?"+ + "\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000\u0000"+ + "\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000\u0000"+ + "\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000\u0000"+ + "\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000\u0000"+ + "\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243\u0244"+ + "\n\u0004\u0000\u0000\u0244\u0245\u0005K\u0000\u0000\u0245\u0247\u0003"+ + "z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000\u0000"+ + "\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000"+ + "\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000\u0000"+ + "\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000\u024c"+ + "\u024e\u0005G\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e"+ + "\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250"+ + "\u0005F\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001\u0000"+ + "\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005G\u0000\u0000"+ + "\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000"+ + "\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005M\u0000\u0000\u0257"+ + "\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259\u024b"+ + "\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a}\u0001"+ + "\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005<\u0000"+ + "\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000\u0000\u0000"+ + "\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000\u0000\u0000"+ + "\u0260\u0261\u0005=\u0000\u0000\u0261\u0262\u0003\u008eG\u0000\u0262\u007f"+ + "\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264\u0265\u0003"+ + "\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003\u0082A"+ + "\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000\u0000"+ + "\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000\u0000"+ + "\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084B\u0000"+ + "\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003\u026e"+ + "\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ + "\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271\u0272"+ + "\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274\n"+ + "\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ + "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ + "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ + "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ + "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ + "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ + "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005c\u0000\u0000\u0280\u0281"+ + "\u0003z=\u0000\u0281\u0282\u0005d\u0000\u0000\u0282\u0284\u0001\u0000"+ + "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ + "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ + "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ + "\u0000\u0286\u0287\u0005<\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ + "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ + "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ + "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ + "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005c\u0000\u0000\u028f\u029d"+ + "\u0005Y\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005>\u0000"+ + "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ + "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ + "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ + "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005>\u0000\u0000\u0299\u029b"+ + "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ + "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ + "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ + "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ + "d\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ + "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005\\\u0000\u0000"+ + "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005>\u0000\u0000\u02a5\u02a7"+ + "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ + "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ + "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ + "\u0000\u0000\u0000\u02ab\u02ac\u0005]\u0000\u0000\u02ac\u008b\u0001\u0000"+ + "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005=\u0000\u0000"+ + "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ + "\u02dc\u0005H\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ + "e\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ + "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ + "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ + "\u0005a\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005>"+ + "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ + "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ + "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ + "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000"+ + "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005a\u0000\u0000\u02c6"+ + "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005>\u0000\u0000\u02c8\u02ca\u0003"+ + "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ + "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ + "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ + "\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ + "\u0000\u02d0\u02d1\u0005a\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ + "\u02d3\u0005>\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ + "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ + "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ + "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ + "b\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ + "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ + "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ + "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ + "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ + "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ + "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ + "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ + "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ + "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ + "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ + "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ + "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ + "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ + "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ + "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ + "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ + "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005r"+ + "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ + "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ + "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005J\u0000\u0000\u02f9"+ + "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005>\u0000\u0000\u02fb\u02fd\u0003"+ + "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ + "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ + "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ + "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ + "\u0000F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ + "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ + "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ + "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u0218\u0228\u0231\u0239\u023e\u0246"+ + "\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a\u0295"+ + "\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { From 640d9b50da34fb1c3989e6a3f171ff42a67a671b Mon Sep 17 00:00:00 2001 From: Mridula Date: Tue, 10 Jun 2025 18:54:56 +0100 Subject: [PATCH 044/102] Add Cluster Feature for L2 Norm (#129181) * propgating retrievers to inner retrievers * test feature taken care of * Small changes in concurrent multipart upload interfaces (#128977) Small changes in BlobContainer interface and wrapper. Relates ES-11815 * Unmute FollowingEngineTests#testProcessOnceOnPrimary() test (#129054) The reason the test fails is that operations contained _seq_no field with different doc value types (with no skippers and with skippers) and this isn't allowed, since field types need to be consistent in a Lucene index. The initial operations were generated not knowing about the fact the index mode was set to logsdb or time_series. Causing the operations to not have doc value skippers. However when replaying the operations via following engine, the operations did have doc value skippers. The fix is to set `index.seq_no.index_options` to `points_and_doc_values`, so that the initial operations are indexed without doc value skippers. This test doesn't gain anything from storing seqno with doc value skippers, so there is no loss of testing coverage. Closes #128541 * [Build] Add support for publishing to maven central (#128659) This ensures we package an aggregation zip with all artifacts we want to publish to maven central as part of a release. Running zipAggregation will produce a zip file in the build/nmcp/zip folder. The content of this zip is meant to match the maven artifacts we have currently declared as dra maven artifacts. * ESQL: Check for errors while loading blocks (#129016) Runs a sanity check after loading a block of values. Previously we were doing a quick check if assertions were enabled. Now we do two quick checks all the time. Better - we attach information about how a block was loaded when there's a problem. Relates to #128959 * Make `PhaseCacheManagementTests` project-aware (#129047) The functionality in `PhaseCacheManagement` was already project-aware, but these tests were still using deprecated methods. * Vector test tools (#128934) This adds some testing tools for verifying vector recall and latency directly without having to spin up an entire ES node and running a rally track. Its pretty barebones and takes inspiration from lucene-util, but I wanted access to our own formats and tooling to make our lives easier. Here is an example config file. This will build the initial index, run queries at num_candidates: 50, then again at num_candidates 100 (without reindexing, and re-using the cached nearest neighbors). ``` [{ "doc_vectors" : "path", "query_vectors" : "path", "num_docs" : 10000, "num_queries" : 10, "index_type" : "hnsw", "num_candidates" : 50, "k" : 10, "hnsw_m" : 16, "hnsw_ef_construction" : 200, "index_threads" : 4, "reindex" : true, "force_merge" : false, "vector_space" : "maximum_inner_product", "dimensions" : 768 }, { "doc_vectors" : "path", "query_vectors" : "path", "num_docs" : 10000, "num_queries" : 10, "index_type" : "hnsw", "num_candidates" : 100, "k" : 10, "hnsw_m" : 16, "hnsw_ef_construction" : 200, "vector_space" : "maximum_inner_product", "dimensions" : 768 } ] ``` To execute: ``` ./gradlew :qa:vector:checkVec --args="/Path/to/knn_tester_config.json" ``` Calling `./gradlew :qa:vector:checkVecHelp` gives some guidance on how to use it, additionally providing a way to run it via java directly (useful to bypass gradlew guff). * ES|QL: refactor generative tests (#129028) * Add a test of LOOKUP JOIN against a time series index (#129007) Add a spec test of `LOOKUP JOIN` against a time series index. * Make ILM `ClusterStateWaitStep` project-aware (#129042) This is part of an iterative process to make ILM project-aware. * Mute org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT test {lookup-join.LookupJoinOnTimeSeriesIndex ASYNC} #129078 * Remove `ClusterState` param from ILM `AsyncBranchingStep` (#129076) The `ClusterState` parameter of the `asyncPredicate` is not used anywhere. * Mute org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT test {lookup-join.LookupJoinOnTimeSeriesIndex SYNC} #129082 * Mute org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT test {p0=upgraded_cluster/70_ilm/Test Lifecycle Still There And Indices Are Still Managed} #129097 * Mute org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT test {p0=upgraded_cluster/90_ml_data_frame_analytics_crud/Get mixed cluster outlier_detection job} #129098 * Mute org.elasticsearch.packaging.test.DockerTests test081SymlinksAreFollowedWithEnvironmentVariableFiles #128867 * Threadpool merge executor is aware of available disk space (#127613) This PR introduces 3 new settings: indices.merge.disk.check_interval, indices.merge.disk.watermark.high, and indices.merge.disk.watermark.high.max_headroom that control if the threadpool merge executor starts executing new merges when the disk space is getting low. The intent of this change is to avoid the situation where in-progress merges exhaust the available disk space on the node's local filesystem. To this end, the thread pool merge executor periodically monitors the available disk space, as well as the current disk space estimates required by all in-progress (currently running) merges on the node, and will NOT schedule any new merges if the disk space is getting low (by default below the 5% limit of the total disk space, or 100 GB, whichever is smaller (same as the disk allocation flood stage level)). * Add option to include or exclude vectors from _source retrieval (#128735) This PR introduces a new include_vectors option to the _source retrieval context. When set to false, vectors are excluded from the returned _source. This is especially efficient when used with synthetic source, as it avoids loading vector fields entirely. By default, vectors remain included unless explicitly excluded. * Remove direct minScore propagation to inner retrievers * cleaned up skip * Mute org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests testAvailableDiskSpaceMonitorWhenFileSystemStatErrors #129149 * Add transport version for ML inference Mistral chat completion (#129033) * Add transport version for ML inference Mistral chat completion * Add changelog for Mistral Chat Completion version fix * Revert "Add changelog for Mistral Chat Completion version fix" This reverts commit 7a57416bdc65805d5303ee3ee7db3368aad89689. * Correct index path validation (#129144) All we care about is if reindex is true or false. We shouldn't worry about force merge. Because if reindex is true, we will create the directory, if its false, we won't. * Mute org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests testUnavailableBudgetBlocksNewMergeTasksFromStartingExecution #129148 * Implemented completion task for Google VertexAI (#128694) * Google Vertex AI completion model, response entity and tests * Fixed GoogleVertexAiServiceTest for Service configuration * Changelog * Removed downcasting and using `moveToFirstToken` * Create GoogleVertexAiChatCompletionResponseHandler for streaming and non streaming responses * Added unit tests * PR feedback * Removed googlevertexaicompletion model. Using just GoogleVertexAiChatCompletionModel for completion and chat completion * Renamed uri -> nonStreamingUri. Added streamingUri and getters in GoogleVertexAiChatCompletionModel * Moved rateLimitGroupHashing to subclasses of GoogleVertexAiModel * Fixed rate limit has of GoogleVertexAiRerankModel and refactored uri for GoogleVertexAiUnifiedChatCompletionRequest --------- Co-authored-by: lhoet-google Co-authored-by: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> * Added cluster feature to yaml * Node feature added * Duplicate line - result of merge removed * Update docs/changelog/129181.yaml * Update 129181.yaml --------- Co-authored-by: Tanguy Leroux Co-authored-by: Martijn van Groningen Co-authored-by: Rene Groeschke Co-authored-by: Nik Everett Co-authored-by: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Co-authored-by: Benjamin Trent Co-authored-by: Luigi Dell'Aquila Co-authored-by: Bogdan Pintea Co-authored-by: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Co-authored-by: Albert Zaharovits Co-authored-by: Jim Ferenczi Co-authored-by: Jan-Kazlouski-elastic Co-authored-by: Leonardo Hoet <55866308+leo-hoet@users.noreply.github.com> Co-authored-by: lhoet-google Co-authored-by: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> --- docs/changelog/129181.yaml | 5 +++++ .../java/org/elasticsearch/xpack/rank/RankRRFFeatures.java | 3 ++- .../elasticsearch/xpack/rank/linear/L2ScoreNormalizer.java | 3 +++ .../rest-api-spec/test/linear/10_linear_retriever.yml | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/129181.yaml diff --git a/docs/changelog/129181.yaml b/docs/changelog/129181.yaml new file mode 100644 index 0000000000000..b19c2c8c32c30 --- /dev/null +++ b/docs/changelog/129181.yaml @@ -0,0 +1,5 @@ +pr: 129181 +summary: Add Cluster Feature for L2 Norm +area: "Search" +type: bug +issues: [] diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java index 5119c4ee3e7e0..053013064b317 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java @@ -13,6 +13,7 @@ import java.util.Set; import static org.elasticsearch.search.retriever.CompoundRetrieverBuilder.INNER_RETRIEVERS_FILTER_SUPPORT; +import static org.elasticsearch.xpack.rank.linear.L2ScoreNormalizer.LINEAR_RETRIEVER_L2_NORM; import static org.elasticsearch.xpack.rank.linear.MinMaxScoreNormalizer.LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX; public class RankRRFFeatures implements FeatureSpecification { @@ -26,6 +27,6 @@ public Set getFeatures() { @Override public Set getTestFeatures() { - return Set.of(INNER_RETRIEVERS_FILTER_SUPPORT, LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX); + return Set.of(INNER_RETRIEVERS_FILTER_SUPPORT, LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX, LINEAR_RETRIEVER_L2_NORM); } } diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/L2ScoreNormalizer.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/L2ScoreNormalizer.java index c05cdfe7cf8f9..34c0a48f15a0d 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/L2ScoreNormalizer.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/L2ScoreNormalizer.java @@ -9,6 +9,7 @@ package org.elasticsearch.xpack.rank.linear; import org.apache.lucene.search.ScoreDoc; +import org.elasticsearch.features.NodeFeature; /** * A score normalizer that applies L2 normalization to a set of scores. @@ -25,6 +26,8 @@ public class L2ScoreNormalizer extends ScoreNormalizer { private static final float EPSILON = 1e-6f; + public static final NodeFeature LINEAR_RETRIEVER_L2_NORM = new NodeFeature("linear_retriever.l2_norm"); + public L2ScoreNormalizer() {} @Override diff --git a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml index b588febe14c22..8797187940b52 100644 --- a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml +++ b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml @@ -266,6 +266,9 @@ setup: --- "should normalize initial scores with l2_norm": + - requires: + cluster_features: [ "linear_retriever.l2_norm" ] + reason: "Requires l2_norm normalization support in linear retriever" - do: search: index: test @@ -320,6 +323,9 @@ setup: --- "should handle all zero scores in normalization": + - requires: + cluster_features: [ "linear_retriever.l2_norm" ] + reason: "Requires l2_norm normalization support in linear retriever" - do: search: index: test From 29f3079c5557af9096ca926e67f18ef8aa3a14ad Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Tue, 10 Jun 2025 20:06:24 +0200 Subject: [PATCH 045/102] Fix DRA dependenciesInfo task dependency resolution (#129209) --- .../gradle/internal/DependenciesInfoPlugin.java | 7 +++++++ distribution/build.gradle | 3 +++ 2 files changed, 10 insertions(+) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesInfoPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesInfoPlugin.java index 9bff87497c573..f687c342e2e5a 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesInfoPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesInfoPlugin.java @@ -15,9 +15,13 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.attributes.Category; +import org.gradle.api.attributes.Usage; import org.gradle.api.plugins.JavaPlugin; public class DependenciesInfoPlugin implements Plugin { + + public static String USAGE_ATTRIBUTE = "DependenciesInfo"; + @Override public void apply(final Project project) { project.getPlugins().apply(CompileOnlyResolvePlugin.class); @@ -43,6 +47,9 @@ public void apply(final Project project) { ) ); + dependenciesInfoFilesConfiguration.attributes( + attributes -> attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, USAGE_ATTRIBUTE)) + ); project.getArtifacts().add("dependenciesInfoFiles", depsInfo); } diff --git a/distribution/build.gradle b/distribution/build.gradle index 821b341977b72..fa6223d30e63b 100644 --- a/distribution/build.gradle +++ b/distribution/build.gradle @@ -31,6 +31,9 @@ configurations { attributes { attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.DOCUMENTATION)) } + attributes { + attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, DependenciesInfoPlugin.USAGE_ATTRIBUTE)) + } } featuresMetadata { attributes { From 35f831522f56ff42e0037618781b720a6b7debce Mon Sep 17 00:00:00 2001 From: John Wagster Date: Tue, 10 Jun 2025 15:19:27 -0500 Subject: [PATCH 046/102] IVF Hierarchical KMeans Flush & Merge (#128675) added hierarchical kmeans as a clustering algorithm to better partitionin the space when running ivf on flush and merge --- .../elasticsearch/test/knn/KnnSearcher.java | 3 +- .../codec/vectors/CentroidAssignments.java | 46 ++ .../vectors/DefaultIVFVectorsReader.java | 9 - .../vectors/DefaultIVFVectorsWriter.java | 616 ++++-------------- .../index/codec/vectors/IVFVectorsReader.java | 17 - .../index/codec/vectors/IVFVectorsWriter.java | 122 ++-- .../index/codec/vectors/KMeans.java | 494 -------------- .../cluster/FloatVectorValuesSlice.java | 52 ++ .../vectors/cluster/HierarchicalKMeans.java | 197 ++++++ .../vectors/cluster/KMeansIntermediate.java | 45 ++ .../codec/vectors/cluster/KMeansLocal.java | 306 +++++++++ .../codec/vectors/cluster/KMeansResult.java | 48 ++ .../vectors/{ => cluster}/NeighborQueue.java | 4 +- .../index/codec/vectors/KMeansTests.java | 154 ----- .../cluster/HierarchicalKMeansTests.java | 70 ++ .../vectors/cluster/KMeansLocalTests.java | 127 ++++ .../{ => cluster}/NeighborQueueTests.java | 6 +- 17 files changed, 1089 insertions(+), 1227 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/CentroidAssignments.java delete mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/KMeans.java create mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/FloatVectorValuesSlice.java create mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeans.java create mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansIntermediate.java create mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocal.java create mode 100644 server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansResult.java rename server/src/main/java/org/elasticsearch/index/codec/vectors/{ => cluster}/NeighborQueue.java (98%) delete mode 100644 server/src/test/java/org/elasticsearch/index/codec/vectors/KMeansTests.java create mode 100644 server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeansTests.java create mode 100644 server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocalTests.java rename server/src/test/java/org/elasticsearch/index/codec/vectors/{ => cluster}/NeighborQueueTests.java (97%) diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java index b0738a6ea5bfb..5a951774ce22b 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java @@ -276,12 +276,11 @@ TopDocs doVectorQuery(byte[] vector, IndexSearcher searcher) throws IOException TopDocs doVectorQuery(float[] vector, IndexSearcher searcher) throws IOException { Query knnQuery; int topK = this.topK; - int efSearch = this.efSearch; if (overSamplingFactor > 1f) { // oversample the topK results to get more candidates for the final result topK = (int) Math.ceil(topK * overSamplingFactor); - efSearch = Math.max(topK, efSearch); } + int efSearch = Math.max(topK, this.efSearch); if (indexType == KnnIndexTester.IndexType.IVF) { knnQuery = new IVFKnnFloatVectorQuery(VECTOR_FIELD, vector, topK, efSearch, null, nProbe); } else { diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/CentroidAssignments.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/CentroidAssignments.java new file mode 100644 index 0000000000000..7c0c79e6ab6ca --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/CentroidAssignments.java @@ -0,0 +1,46 @@ +/* + * 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.index.codec.vectors; + +import org.apache.lucene.internal.hppc.IntArrayList; + +final class CentroidAssignments { + + private final int numCentroids; + private final float[][] cachedCentroids; + private final IntArrayList[] assignmentsByCluster; + + private CentroidAssignments(int numCentroids, float[][] cachedCentroids, IntArrayList[] assignmentsByCluster) { + this.numCentroids = numCentroids; + this.cachedCentroids = cachedCentroids; + this.assignmentsByCluster = assignmentsByCluster; + } + + CentroidAssignments(float[][] centroids, IntArrayList[] assignmentsByCluster) { + this(centroids.length, centroids, assignmentsByCluster); + } + + CentroidAssignments(int numCentroids, IntArrayList[] assignmentsByCluster) { + this(numCentroids, null, assignmentsByCluster); + } + + // Getters and setters + public int numCentroids() { + return numCentroids; + } + + public float[][] cachedCentroids() { + return cachedCentroids; + } + + public IntArrayList[] assignmentsByCluster() { + return assignmentsByCluster; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java index e09cf474d09ea..36a7c2084a4a5 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java @@ -112,15 +112,6 @@ public float score(int centroidOrdinal) throws IOException { }; } - @Override - protected FloatVectorValues getCentroids(IndexInput indexInput, int numCentroids, FieldInfo info) { - FieldEntry entry = fields.get(info.number); - if (entry == null) { - return null; - } - return new OffHeapCentroidFloatVectorValues(numCentroids, indexInput, info.getVectorDimension()); - } - @Override NeighborQueue scorePostingLists(FieldInfo fieldInfo, KnnCollector knnCollector, CentroidQueryScorer centroidQueryScorer, int nProbe) throws IOException { diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java index 1c431b01e611c..2527a91074e7e 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java @@ -14,21 +14,19 @@ import org.apache.lucene.index.FloatVectorValues; import org.apache.lucene.index.MergeState; import org.apache.lucene.index.SegmentWriteState; -import org.apache.lucene.index.VectorSimilarityFunction; import org.apache.lucene.internal.hppc.IntArrayList; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; import org.apache.lucene.util.InfoStream; import org.apache.lucene.util.VectorUtil; import org.apache.lucene.util.quantization.OptimizedScalarQuantizer; +import org.elasticsearch.index.codec.vectors.cluster.HierarchicalKMeans; +import org.elasticsearch.index.codec.vectors.cluster.KMeansResult; import org.elasticsearch.simdvec.ES91OSQVectorsScorer; -import org.elasticsearch.simdvec.ESVectorUtil; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; import static org.apache.lucene.codecs.lucene102.Lucene102BinaryQuantizedVectorsFormat.INDEX_BITS; import static org.apache.lucene.util.quantization.OptimizedScalarQuantizer.discretize; @@ -36,16 +34,12 @@ import static org.elasticsearch.index.codec.vectors.IVFVectorsFormat.IVF_VECTOR_COMPONENT; /** - * Default implementation of {@link IVFVectorsWriter}. It uses {@link KMeans} algorithm to - * partition the vector space, and then stores the centroids an posting list in a sequential + * Default implementation of {@link IVFVectorsWriter}. It uses {@link HierarchicalKMeans} algorithm to + * partition the vector space, and then stores the centroids and posting list in a sequential * fashion. */ public class DefaultIVFVectorsWriter extends IVFVectorsWriter { - static final float SOAR_LAMBDA = 1.0f; - // What percentage of the centroids do we do a second check on for SOAR assignment - static final float EXT_SOAR_LIMIT_CHECK_RATIO = 0.10f; - private final int vectorPerCluster; public DefaultIVFVectorsWriter(SegmentWriteState state, FlatVectorsWriter rawVectorDelegate, int vectorPerCluster) throws IOException { @@ -53,77 +47,81 @@ public DefaultIVFVectorsWriter(SegmentWriteState state, FlatVectorsWriter rawVec this.vectorPerCluster = vectorPerCluster; } - @Override - CentroidAssignmentScorer calculateAndWriteCentroids( - FieldInfo fieldInfo, - FloatVectorValues floatVectorValues, - IndexOutput centroidOutput, - float[] globalCentroid - ) throws IOException { - if (floatVectorValues.size() == 0) { - return CentroidAssignmentScorer.EMPTY; - } - // calculate the centroids - int maxNumClusters = ((floatVectorValues.size() - 1) / vectorPerCluster) + 1; - int desiredClusters = (int) Math.max(Math.sqrt(floatVectorValues.size()), maxNumClusters); - final KMeans.Results kMeans = KMeans.cluster( - floatVectorValues, - desiredClusters, - false, - 42L, - KMeans.KmeansInitializationMethod.PLUS_PLUS, - null, - fieldInfo.getVectorSimilarityFunction() == VectorSimilarityFunction.COSINE, - 1, - 15, - desiredClusters * 256 - ); - float[][] centroids = kMeans.centroids(); - // write them - writeCentroids(centroids, fieldInfo, globalCentroid, centroidOutput); - return new OnHeapCentroidAssignmentScorer(centroids); - } - @Override long[] buildAndWritePostingsLists( FieldInfo fieldInfo, - InfoStream infoStream, - CentroidAssignmentScorer randomCentroidScorer, + CentroidSupplier centroidSupplier, FloatVectorValues floatVectorValues, - IndexOutput postingsOutput + IndexOutput postingsOutput, + InfoStream infoStream, + IntArrayList[] assignmentsByCluster ) throws IOException { - IntArrayList[] clusters = new IntArrayList[randomCentroidScorer.size()]; - for (int i = 0; i < randomCentroidScorer.size(); i++) { - clusters[i] = new IntArrayList(floatVectorValues.size() / randomCentroidScorer.size() / 4); - } - assignCentroids(randomCentroidScorer, floatVectorValues, clusters); - if (infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - printClusterQualityStatistics(clusters, infoStream); - } // write the posting lists - final long[] offsets = new long[randomCentroidScorer.size()]; + final long[] offsets = new long[centroidSupplier.size()]; OptimizedScalarQuantizer quantizer = new OptimizedScalarQuantizer(fieldInfo.getVectorSimilarityFunction()); BinarizedFloatVectorValues binarizedByteVectorValues = new BinarizedFloatVectorValues(floatVectorValues, quantizer); DocIdsWriter docIdsWriter = new DocIdsWriter(); - for (int i = 0; i < randomCentroidScorer.size(); i++) { - float[] centroid = randomCentroidScorer.centroid(i); + + for (int c = 0; c < centroidSupplier.size(); c++) { + float[] centroid = centroidSupplier.centroid(c); binarizedByteVectorValues.centroid = centroid; - // TODO sort by distance to the centroid - IntArrayList cluster = clusters[i]; + // TODO: add back in sorting vectors by distance to centroid + IntArrayList cluster = assignmentsByCluster[c]; // TODO align??? - offsets[i] = postingsOutput.getFilePointer(); + offsets[c] = postingsOutput.getFilePointer(); int size = cluster.size(); postingsOutput.writeVInt(size); postingsOutput.writeInt(Float.floatToIntBits(VectorUtil.dotProduct(centroid, centroid))); // TODO we might want to consider putting the docIds in a separate file // to aid with only having to fetch vectors from slower storage when they are required // keeping them in the same file indicates we pull the entire file into cache - docIdsWriter.writeDocIds(j -> floatVectorValues.ordToDoc(cluster.get(j)), cluster.size(), postingsOutput); + docIdsWriter.writeDocIds(j -> floatVectorValues.ordToDoc(cluster.get(j)), size, postingsOutput); writePostingList(cluster, postingsOutput, binarizedByteVectorValues); } + + if (infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { + printClusterQualityStatistics(assignmentsByCluster, infoStream); + } + return offsets; } + private static void printClusterQualityStatistics(IntArrayList[] clusters, InfoStream infoStream) { + float min = Float.MAX_VALUE; + float max = Float.MIN_VALUE; + float mean = 0; + float m2 = 0; + // iteratively compute the variance & mean + int count = 0; + for (IntArrayList cluster : clusters) { + count += 1; + if (cluster == null) { + continue; + } + float delta = cluster.size() - mean; + mean += delta / count; + m2 += delta * (cluster.size() - mean); + min = Math.min(min, cluster.size()); + max = Math.max(max, cluster.size()); + } + float variance = m2 / (clusters.length - 1); + infoStream.message( + IVF_VECTOR_COMPONENT, + "Centroid count: " + + clusters.length + + " min: " + + min + + " max: " + + max + + " mean: " + + mean + + " stdDev: " + + Math.sqrt(variance) + + " variance: " + + variance + ); + } + private void writePostingList(IntArrayList cluster, IndexOutput postingsOutput, BinarizedFloatVectorValues binarizedByteVectorValues) throws IOException { int limit = cluster.size() - ES91OSQVectorsScorer.BULK_SIZE + 1; @@ -173,13 +171,8 @@ private void writePostingList(IntArrayList cluster, IndexOutput postingsOutput, } @Override - CentroidAssignmentScorer createCentroidScorer( - IndexInput centroidsInput, - int numCentroids, - FieldInfo fieldInfo, - float[] globalCentroid - ) { - return new OffHeapCentroidAssignmentScorer(centroidsInput, numCentroids, fieldInfo); + CentroidSupplier createCentroidSupplier(IndexInput centroidsInput, int numCentroids, FieldInfo fieldInfo, float[] globalCentroid) { + return new OffHeapCentroidSupplier(centroidsInput, numCentroids, fieldInfo); } static void writeCentroids(float[][] centroids, FieldInfo fieldInfo, float[] globalCentroid, IndexOutput centroidOutput) @@ -188,24 +181,8 @@ static void writeCentroids(float[][] centroids, FieldInfo fieldInfo, float[] glo byte[] quantizedScratch = new byte[fieldInfo.getVectorDimension()]; float[] centroidScratch = new float[fieldInfo.getVectorDimension()]; // TODO do we want to store these distances as well for future use? - float[] distances = new float[centroids.length]; - for (int i = 0; i < centroids.length; i++) { - distances[i] = VectorUtil.squareDistance(centroids[i], globalCentroid); - } - // sort the centroids by distance to globalCentroid, nearest (smallest distance), to furthest - // (largest) - for (int i = 0; i < centroids.length; i++) { - for (int j = i + 1; j < centroids.length; j++) { - if (distances[i] > distances[j]) { - float[] tmp = centroids[i]; - centroids[i] = centroids[j]; - centroids[j] = tmp; - float tmpDistance = distances[i]; - distances[i] = distances[j]; - distances[j] = tmpDistance; - } - } - } + // TODO: sort centroids by global centroid (was doing so previously here) + // TODO: sorting tanks recall possibly because centroids ordinals no longer are aligned for (float[] centroid : centroids) { System.arraycopy(centroid, 0, centroidScratch, 0, centroid.length); OptimizedScalarQuantizer.QuantizationResult result = osq.scalarQuantize( @@ -223,190 +200,60 @@ static void writeCentroids(float[][] centroids, FieldInfo fieldInfo, float[] glo } } - static float[][] gatherInitCentroids( - List centroidList, - List segmentCentroids, - int desiredClusters, + CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, - MergeState mergeState + FloatVectorValues floatVectorValues, + IndexOutput centroidOutput, + MergeState mergeState, + float[] globalCentroid ) throws IOException { - if (centroidList.size() == 0) { - return null; - } - long startTime = System.nanoTime(); - // sort centroid list by floatvector size - FloatVectorValues baseSegment = centroidList.get(0); - for (var l : centroidList) { - if (l.size() > baseSegment.size()) { - baseSegment = l; - } - } - float[] scratch = new float[fieldInfo.getVectorDimension()]; - float minimumDistance = Float.MAX_VALUE; - for (int j = 0; j < baseSegment.size(); j++) { - System.arraycopy(baseSegment.vectorValue(j), 0, scratch, 0, baseSegment.dimension()); - for (int k = j + 1; k < baseSegment.size(); k++) { - float d = VectorUtil.squareDistance(scratch, baseSegment.vectorValue(k)); - if (d < minimumDistance) { - minimumDistance = d; - } - } - } - if (mergeState.infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - mergeState.infoStream.message( - IVF_VECTOR_COMPONENT, - "Agglomerative cluster min distance: " + minimumDistance + " From biggest segment: " + baseSegment.size() - ); - } - int[] labels = new int[segmentCentroids.size()]; - // loop over segments - int clusterIdx = 0; - // keep track of all inter-centroid distances, - // using less than centroid * centroid space (e.g. not keeping track of duplicates) - for (int i = 0; i < segmentCentroids.size(); i++) { - if (labels[i] == 0) { - clusterIdx += 1; - labels[i] = clusterIdx; - } - SegmentCentroid segmentCentroid = segmentCentroids.get(i); - System.arraycopy( - centroidList.get(segmentCentroid.segment()).vectorValue(segmentCentroid.centroid), - 0, - scratch, - 0, - baseSegment.dimension() - ); - for (int j = i + 1; j < segmentCentroids.size(); j++) { - float d = VectorUtil.squareDistance( - scratch, - centroidList.get(segmentCentroids.get(j).segment()).vectorValue(segmentCentroids.get(j).centroid()) - ); - if (d < minimumDistance / 2) { - if (labels[j] == 0) { - labels[j] = labels[i]; - } else { - for (int k = 0; k < labels.length; k++) { - if (labels[k] == labels[j]) { - labels[k] = labels[i]; - } - } - } - } - } - } - float[][] initCentroids = new float[clusterIdx][fieldInfo.getVectorDimension()]; - int[] sum = new int[clusterIdx]; - for (int i = 0; i < segmentCentroids.size(); i++) { - SegmentCentroid segmentCentroid = segmentCentroids.get(i); - int label = labels[i]; - FloatVectorValues segment = centroidList.get(segmentCentroid.segment()); - float[] vector = segment.vectorValue(segmentCentroid.centroid); - for (int j = 0; j < vector.length; j++) { - initCentroids[label - 1][j] += (vector[j] * segmentCentroid.centroidSize); - } - sum[label - 1] += segmentCentroid.centroidSize; - } - for (int i = 0; i < initCentroids.length; i++) { - if (sum[i] == 0 || sum[i] == 1) { - continue; - } - for (int j = 0; j < initCentroids[i].length; j++) { - initCentroids[i][j] /= sum[i]; - } - } - if (mergeState.infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - mergeState.infoStream.message( - IVF_VECTOR_COMPONENT, - "Agglomerative cluster time ms: " + ((System.nanoTime() - startTime) / 1000000.0) - ); - mergeState.infoStream.message( - IVF_VECTOR_COMPONENT, - "Gathered initCentroids:" + initCentroids.length + " for desired: " + desiredClusters - ); - } - return initCentroids; + // TODO: take advantage of prior generated clusters from mergeState in the future + return calculateAndWriteCentroids(fieldInfo, floatVectorValues, centroidOutput, mergeState.infoStream, globalCentroid, false); } - record SegmentCentroid(int segment, int centroid, int centroidSize) {} + CentroidAssignments calculateAndWriteCentroids( + FieldInfo fieldInfo, + FloatVectorValues floatVectorValues, + IndexOutput centroidOutput, + InfoStream infoStream, + float[] globalCentroid + ) throws IOException { + return calculateAndWriteCentroids(fieldInfo, floatVectorValues, centroidOutput, infoStream, globalCentroid, true); + } /** - * Calculate the centroids for the given field and write them to the given - * temporary centroid output. - * When merging, we first bootstrap the KMeans algorithm with the centroids contained in the merging segments. - * To prevent centroids that are too similar from having an outsized impact, all centroids that are closer than - * the largest segments intra-cluster distance are merged into a single centroid. - * The resulting centroids are then used to initialize the KMeans algorithm. + * Calculate the centroids for the given field and write them to the given centroid output. + * We use the {@link HierarchicalKMeans} algorithm to partition the space of all vectors across merging segments * * @param fieldInfo merging field info * @param floatVectorValues the float vector values to merge - * @param temporaryCentroidOutput the temporary centroid output - * @param mergeState the merge state + * @param centroidOutput the centroid output + * @param infoStream the merge state * @param globalCentroid the global centroid, calculated by this method and used to quantize the centroids - * @return the number of centroids written + * @param cacheCentroids whether the centroids are kept or discarded once computed + * @return the vector assignments, soar assignments, and if asked the centroids themselves that were computed * @throws IOException if an I/O error occurs */ - @Override - protected int calculateAndWriteCentroids( + CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, FloatVectorValues floatVectorValues, - IndexOutput temporaryCentroidOutput, - MergeState mergeState, - float[] globalCentroid + IndexOutput centroidOutput, + InfoStream infoStream, + float[] globalCentroid, + boolean cacheCentroids ) throws IOException { - if (floatVectorValues.size() == 0) { - return 0; - } - int maxNumClusters = ((floatVectorValues.size() - 1) / vectorPerCluster) + 1; - int desiredClusters = (int) Math.max(Math.sqrt(floatVectorValues.size()), maxNumClusters); - // init centroids from merge state - List centroidList = new ArrayList<>(); - List segmentCentroids = new ArrayList<>(desiredClusters); - - int segmentIdx = 0; - for (var reader : mergeState.knnVectorsReaders) { - IVFVectorsReader ivfVectorsReader = IVFVectorsFormat.getIVFReader(reader, fieldInfo.name); - if (ivfVectorsReader == null) { - continue; - } - FloatVectorValues centroid = ivfVectorsReader.getCentroids(fieldInfo); - if (centroid == null) { - continue; - } - centroidList.add(centroid); - for (int i = 0; i < centroid.size(); i++) { - int size = ivfVectorsReader.centroidSize(fieldInfo.name, i); - if (size == 0) { - continue; - } - segmentCentroids.add(new SegmentCentroid(segmentIdx, i, size)); - } - segmentIdx++; - } - - float[][] initCentroids = gatherInitCentroids(centroidList, segmentCentroids, desiredClusters, fieldInfo, mergeState); - - // FIXME: run a custom version of KMeans that is just better... long nanoTime = System.nanoTime(); - final KMeans.Results kMeans = KMeans.cluster( - floatVectorValues, - desiredClusters, - false, - 42L, - KMeans.KmeansInitializationMethod.PLUS_PLUS, - initCentroids, - fieldInfo.getVectorSimilarityFunction() == VectorSimilarityFunction.COSINE, - 1, - 5, - desiredClusters * 64 - ); - if (mergeState.infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - mergeState.infoStream.message(IVF_VECTOR_COMPONENT, "KMeans time ms: " + ((System.nanoTime() - nanoTime) / 1000000.0)); - } - float[][] centroids = kMeans.centroids(); - // write them - // calculate the global centroid from all the centroids: + // TODO: consider hinting / bootstrapping hierarchical kmeans with the prior segments centroids + KMeansResult kMeansResult = new HierarchicalKMeans(floatVectorValues.dimension()).cluster(floatVectorValues, vectorPerCluster); + float[][] centroids = kMeansResult.centroids(); + int[] assignments = kMeansResult.assignments(); + int[] soarAssignments = kMeansResult.soarAssignments(); + + // TODO: for flush we are doing this over the vectors and here centroids which seems duplicative + // preliminary tests suggest recall is good using only centroids but need to do further evaluation + // TODO: push this logic into vector util? for (float[] centroid : centroids) { for (int j = 0; j < centroid.length; j++) { globalCentroid[j] += centroid[j]; @@ -415,197 +262,41 @@ protected int calculateAndWriteCentroids( for (int j = 0; j < globalCentroid.length; j++) { globalCentroid[j] /= centroids.length; } - writeCentroids(centroids, fieldInfo, globalCentroid, temporaryCentroidOutput); - return centroids.length; - } - @Override - long[] buildAndWritePostingsLists( - FieldInfo fieldInfo, - CentroidAssignmentScorer centroidAssignmentScorer, - FloatVectorValues floatVectorValues, - IndexOutput postingsOutput, - MergeState mergeState - ) throws IOException { - IntArrayList[] clusters = new IntArrayList[centroidAssignmentScorer.size()]; - for (int i = 0; i < centroidAssignmentScorer.size(); i++) { - clusters[i] = new IntArrayList(floatVectorValues.size() / centroidAssignmentScorer.size() / 4); - } - long nanoTime = System.nanoTime(); - // Can we do a pre-filter by finding the nearest centroids to the original vector centroids? - // We need to be careful on vecOrd vs. doc as we need random access to the raw vector for posting list writing - assignCentroids(centroidAssignmentScorer, floatVectorValues, clusters); - if (mergeState.infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - mergeState.infoStream.message(IVF_VECTOR_COMPONENT, "assignCentroids time ms: " + ((System.nanoTime() - nanoTime) / 1000000.0)); - } - - if (mergeState.infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - printClusterQualityStatistics(clusters, mergeState.infoStream); - } - // write the posting lists - final long[] offsets = new long[centroidAssignmentScorer.size()]; - OptimizedScalarQuantizer quantizer = new OptimizedScalarQuantizer(fieldInfo.getVectorSimilarityFunction()); - BinarizedFloatVectorValues binarizedByteVectorValues = new BinarizedFloatVectorValues(floatVectorValues, quantizer); - DocIdsWriter docIdsWriter = new DocIdsWriter(); - for (int i = 0; i < centroidAssignmentScorer.size(); i++) { - float[] centroid = centroidAssignmentScorer.centroid(i); - binarizedByteVectorValues.centroid = centroid; - // TODO: sort by distance to the centroid - IntArrayList cluster = clusters[i]; - // TODO align??? - offsets[i] = postingsOutput.getFilePointer(); - int size = cluster.size(); - postingsOutput.writeVInt(size); - postingsOutput.writeInt(Float.floatToIntBits(VectorUtil.dotProduct(centroid, centroid))); - // TODO we might want to consider putting the docIds in a separate file - // to aid with only having to fetch vectors from slower storage when they are required - // keeping them in the same file indicates we pull the entire file into cache - docIdsWriter.writeDocIds(j -> floatVectorValues.ordToDoc(cluster.get(j)), size, postingsOutput); - writePostingList(cluster, postingsOutput, binarizedByteVectorValues); - } - return offsets; - } + // write centroids + writeCentroids(centroids, fieldInfo, globalCentroid, centroidOutput); - private static void printClusterQualityStatistics(IntArrayList[] clusters, InfoStream infoStream) { - float min = Float.MAX_VALUE; - float max = Float.MIN_VALUE; - float mean = 0; - float m2 = 0; - // iteratively compute the variance & mean - int count = 0; - for (IntArrayList cluster : clusters) { - count += 1; - if (cluster == null) { - continue; - } - float delta = cluster.size() - mean; - mean += delta / count; - m2 += delta * (cluster.size() - mean); - min = Math.min(min, cluster.size()); - max = Math.max(max, cluster.size()); + if (infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { + infoStream.message( + IVF_VECTOR_COMPONENT, + "calculate centroids and assign vectors time ms: " + ((System.nanoTime() - nanoTime) / 1000000.0) + ); + infoStream.message(IVF_VECTOR_COMPONENT, "final centroid count: " + centroids.length); } - float variance = m2 / (clusters.length - 1); - infoStream.message( - IVF_VECTOR_COMPONENT, - "Centroid count: " - + clusters.length - + " min: " - + min - + " max: " - + max - + " mean: " - + mean - + " stdDev: " - + Math.sqrt(variance) - + " variance: " - + variance - ); - } - static void assignCentroids(CentroidAssignmentScorer scorer, FloatVectorValues vectors, IntArrayList[] clusters) throws IOException { - int numCentroids = scorer.size(); - // we at most will look at the EXT_SOAR_LIMIT_CHECK_RATIO nearest centroids if possible - int soarToCheck = (int) (numCentroids * EXT_SOAR_LIMIT_CHECK_RATIO); - int soarClusterCheckCount = Math.min(numCentroids - 1, soarToCheck); - NeighborQueue neighborsToCheck = new NeighborQueue(soarClusterCheckCount + 1, true); - OrdScoreIterator ordScoreIterator = new OrdScoreIterator(soarClusterCheckCount + 1); - float[] scratch = new float[vectors.dimension()]; - for (int docID = 0; docID < vectors.size(); docID++) { - float[] vector = vectors.vectorValue(docID); - scorer.setScoringVector(vector); - int bestCentroid = 0; - float bestScore = Float.MAX_VALUE; - if (numCentroids > 1) { - for (short c = 0; c < numCentroids; c++) { - float squareDist = scorer.score(c); - neighborsToCheck.insertWithOverflow(c, squareDist); + IntArrayList[] assignmentsByCluster = new IntArrayList[centroids.length]; + for (int c = 0; c < centroids.length; c++) { + IntArrayList cluster = new IntArrayList(vectorPerCluster); + for (int j = 0; j < assignments.length; j++) { + if (assignments[j] == c) { + cluster.add(j); } - // pop the best - int sz = neighborsToCheck.size(); - int best = neighborsToCheck.consumeNodesAndScoresMin(ordScoreIterator.ords, ordScoreIterator.scores); - // Set the size to the number of neighbors we actually found - ordScoreIterator.setSize(sz); - bestScore = ordScoreIterator.getScore(best); - bestCentroid = ordScoreIterator.getOrd(best); } - clusters[bestCentroid].add(docID); - if (soarClusterCheckCount > 0) { - assignCentroidSOAR( - ordScoreIterator, - docID, - bestCentroid, - scorer.centroid(bestCentroid), - bestScore, - scratch, - scorer, - vector, - clusters - ); - } - neighborsToCheck.clear(); - } - } - - static void assignCentroidSOAR( - OrdScoreIterator centroidsToCheck, - int vecOrd, - int bestCentroidId, - float[] bestCentroid, - float bestScore, - float[] scratch, - CentroidAssignmentScorer scorer, - float[] vector, - IntArrayList[] clusters - ) throws IOException { - ESVectorUtil.subtract(vector, bestCentroid, scratch); - int bestSecondaryCentroid = -1; - float minDist = Float.MAX_VALUE; - for (int i = 0; i < centroidsToCheck.size(); i++) { - float score = centroidsToCheck.getScore(i); - int centroidOrdinal = centroidsToCheck.getOrd(i); - if (centroidOrdinal == bestCentroidId) { - continue; - } - float proj = ESVectorUtil.soarResidual(vector, scorer.centroid(centroidOrdinal), scratch); - score += SOAR_LAMBDA * proj * proj / bestScore; - if (score < minDist) { - bestSecondaryCentroid = centroidOrdinal; - minDist = score; - } - } - if (bestSecondaryCentroid != -1) { - clusters[bestSecondaryCentroid].add(vecOrd); - } - } - - static class OrdScoreIterator { - private final int[] ords; - private final float[] scores; - private int idx = 0; - OrdScoreIterator(int size) { - this.ords = new int[size]; - this.scores = new float[size]; - } - - int setSize(int size) { - if (size > ords.length) { - throw new IllegalArgumentException("size must be <= " + ords.length); + for (int j = 0; j < soarAssignments.length; j++) { + if (soarAssignments[j] == c) { + cluster.add(j); + } } - this.idx = size; - return size; - } - int getOrd(int idx) { - return ords[idx]; + cluster.trimToSize(); + assignmentsByCluster[c] = cluster; } - float getScore(int idx) { - return scores[idx]; - } - - int size() { - return idx; + if (cacheCentroids) { + return new CentroidAssignments(centroids, assignmentsByCluster); + } else { + return new CentroidAssignments(centroids.length, assignmentsByCluster); } } @@ -650,16 +341,25 @@ private void binarize(int ord) throws IOException { } } - static class OffHeapCentroidAssignmentScorer implements CentroidAssignmentScorer { + static void writeQuantizedValue(IndexOutput indexOutput, byte[] binaryValue, OptimizedScalarQuantizer.QuantizationResult corrections) + throws IOException { + indexOutput.writeBytes(binaryValue, binaryValue.length); + indexOutput.writeInt(Float.floatToIntBits(corrections.lowerInterval())); + indexOutput.writeInt(Float.floatToIntBits(corrections.upperInterval())); + indexOutput.writeInt(Float.floatToIntBits(corrections.additionalCorrection())); + assert corrections.quantizedComponentSum() >= 0 && corrections.quantizedComponentSum() <= 0xffff; + indexOutput.writeShort((short) corrections.quantizedComponentSum()); + } + + static class OffHeapCentroidSupplier implements CentroidSupplier { private final IndexInput centroidsInput; private final int numCentroids; private final int dimension; private final float[] scratch; - private float[] q; private final long rawCentroidOffset; private int currOrd = -1; - OffHeapCentroidAssignmentScorer(IndexInput centroidsInput, int numCentroids, FieldInfo info) { + OffHeapCentroidSupplier(IndexInput centroidsInput, int numCentroids, FieldInfo info) { this.centroidsInput = centroidsInput; this.numCentroids = numCentroids; this.dimension = info.getVectorDimension(); @@ -682,55 +382,5 @@ public float[] centroid(int centroidOrdinal) throws IOException { this.currOrd = centroidOrdinal; return scratch; } - - @Override - public void setScoringVector(float[] vector) { - q = vector; - } - - @Override - public float score(int centroidOrdinal) throws IOException { - return VectorUtil.squareDistance(centroid(centroidOrdinal), q); - } - } - - // TODO throw away rawCentroids - static class OnHeapCentroidAssignmentScorer implements CentroidAssignmentScorer { - private final float[][] centroids; - private float[] q; - - OnHeapCentroidAssignmentScorer(float[][] centroids) { - this.centroids = centroids; - } - - @Override - public int size() { - return centroids.length; - } - - @Override - public void setScoringVector(float[] vector) { - q = vector; - } - - @Override - public float[] centroid(int centroidOrdinal) throws IOException { - return centroids[centroidOrdinal]; - } - - @Override - public float score(int centroidOrdinal) throws IOException { - return VectorUtil.squareDistance(centroid(centroidOrdinal), q); - } - } - - static void writeQuantizedValue(IndexOutput indexOutput, byte[] binaryValue, OptimizedScalarQuantizer.QuantizationResult corrections) - throws IOException { - indexOutput.writeBytes(binaryValue, binaryValue.length); - indexOutput.writeInt(Float.floatToIntBits(corrections.lowerInterval())); - indexOutput.writeInt(Float.floatToIntBits(corrections.upperInterval())); - indexOutput.writeInt(Float.floatToIntBits(corrections.additionalCorrection())); - assert corrections.quantizedComponentSum() >= 0 && corrections.quantizedComponentSum() <= 0xffff; - indexOutput.writeShort((short) corrections.quantizedComponentSum()); } } diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java index 1a0a5bd94af35..d5086cf2d479e 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsReader.java @@ -97,23 +97,6 @@ abstract CentroidQueryScorer getCentroidScorer( IndexInput clusters ) throws IOException; - protected abstract FloatVectorValues getCentroids(IndexInput indexInput, int numCentroids, FieldInfo info) throws IOException; - - public FloatVectorValues getCentroids(FieldInfo fieldInfo) throws IOException { - FieldEntry entry = fields.get(fieldInfo.number); - if (entry == null) { - return null; - } - return getCentroids(entry.centroidSlice(ivfCentroids), entry.postingListOffsets.length, fieldInfo); - } - - int centroidSize(String fieldName, int centroidOrdinal) throws IOException { - FieldInfo fieldInfo = state.fieldInfos.fieldInfo(fieldName); - FieldEntry entry = fields.get(fieldInfo.number); - ivfClusters.seek(entry.postingListOffsets[centroidOrdinal]); - return ivfClusters.readVInt(); - } - private static IndexInput openDataInput( SegmentReadState state, int versionMeta, diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java index d6188703881a4..73a0bdd5efa48 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java @@ -11,11 +11,9 @@ import org.apache.lucene.codecs.CodecUtil; import org.apache.lucene.codecs.KnnFieldVectorsWriter; -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.perfield.PerFieldKnnVectorsFormat; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.FloatVectorValues; import org.apache.lucene.index.IndexFileNames; @@ -25,6 +23,7 @@ import org.apache.lucene.index.Sorter; import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.internal.hppc.IntArrayList; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; @@ -123,38 +122,32 @@ public final KnnFieldVectorsWriter addField(FieldInfo fieldInfo) throws IOExc return rawVectorDelegate; } - protected abstract int calculateAndWriteCentroids( + abstract CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, FloatVectorValues floatVectorValues, - IndexOutput temporaryCentroidOutput, + IndexOutput centroidOutput, MergeState mergeState, float[] globalCentroid ) throws IOException; - abstract long[] buildAndWritePostingsLists( - FieldInfo fieldInfo, - CentroidAssignmentScorer scorer, - FloatVectorValues floatVectorValues, - IndexOutput postingsOutput, - MergeState mergeState - ) throws IOException; - - abstract CentroidAssignmentScorer calculateAndWriteCentroids( + abstract CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, FloatVectorValues floatVectorValues, IndexOutput centroidOutput, + InfoStream infoStream, float[] globalCentroid ) throws IOException; abstract long[] buildAndWritePostingsLists( FieldInfo fieldInfo, - InfoStream infoStream, - CentroidAssignmentScorer scorer, + CentroidSupplier centroidSupplier, FloatVectorValues floatVectorValues, - IndexOutput postingsOutput + IndexOutput postingsOutput, + InfoStream infoStream, + IntArrayList[] assignmentsByCluster ) throws IOException; - abstract CentroidAssignmentScorer createCentroidScorer( + abstract CentroidSupplier createCentroidSupplier( IndexInput centroidsInput, int numCentroids, FieldInfo fieldInfo, @@ -166,33 +159,31 @@ public final void flush(int maxDoc, Sorter.DocMap sortMap) throws IOException { rawVectorDelegate.flush(maxDoc, sortMap); for (FieldWriter fieldWriter : fieldWriters) { float[] globalCentroid = new float[fieldWriter.fieldInfo.getVectorDimension()]; - // calculate global centroid - for (var vector : fieldWriter.delegate.getVectors()) { - for (int i = 0; i < globalCentroid.length; i++) { - globalCentroid[i] += vector[i]; - } - } - for (int i = 0; i < globalCentroid.length; i++) { - globalCentroid[i] /= fieldWriter.delegate.getVectors().size(); - } // build a float vector values with random access final FloatVectorValues floatVectorValues = getFloatVectorValues(fieldWriter.fieldInfo, fieldWriter.delegate, maxDoc); // build centroids long centroidOffset = ivfCentroids.alignFilePointer(Float.BYTES); - final CentroidAssignmentScorer centroidAssignmentScorer = calculateAndWriteCentroids( + + final CentroidAssignments centroidAssignments = calculateAndWriteCentroids( fieldWriter.fieldInfo, floatVectorValues, ivfCentroids, + segmentWriteState.infoStream, globalCentroid ); + + CentroidSupplier centroidSupplier = new OnHeapCentroidSupplier(centroidAssignments.cachedCentroids()); + long centroidLength = ivfCentroids.getFilePointer() - centroidOffset; final long[] offsets = buildAndWritePostingsLists( fieldWriter.fieldInfo, - segmentWriteState.infoStream, - centroidAssignmentScorer, + centroidSupplier, floatVectorValues, - ivfClusters + ivfClusters, + segmentWriteState.infoStream, + centroidAssignments.assignmentsByCluster() ); + // write posting lists writeMeta(fieldWriter.fieldInfo, centroidOffset, centroidLength, offsets, globalCentroid); } } @@ -240,16 +231,6 @@ public int ordToDoc(int ord) { }; } - static IVFVectorsReader getIVFReader(KnnVectorsReader vectorsReader, String fieldName) { - if (vectorsReader instanceof PerFieldKnnVectorsFormat.FieldsReader candidateReader) { - vectorsReader = candidateReader.getFieldReader(fieldName); - } - if (vectorsReader instanceof IVFVectorsReader reader) { - return reader; - } - return null; - } - @Override @SuppressForbidden(reason = "require usage of Lucene's IOUtils#deleteFilesIgnoringExceptions(...)") public final void mergeOneField(FieldInfo fieldInfo, MergeState mergeState) throws IOException { @@ -277,22 +258,25 @@ public final void mergeOneField(FieldInfo fieldInfo, MergeState mergeState) thro float[] calculatedGlobalCentroid = new float[fieldInfo.getVectorDimension()]; final FloatVectorValues floatVectorValues = getFloatVectorValues(fieldInfo, in, numVectors); success = false; - CentroidAssignmentScorer centroidAssignmentScorer; long centroidOffset; long centroidLength; String centroidTempName = null; int numCentroids; IndexOutput centroidTemp = null; + CentroidAssignments centroidAssignments; try { centroidTemp = mergeState.segmentInfo.dir.createTempOutput(mergeState.segmentInfo.name, "civf_", IOContext.DEFAULT); centroidTempName = centroidTemp.getName(); - numCentroids = calculateAndWriteCentroids( + + centroidAssignments = calculateAndWriteCentroids( fieldInfo, floatVectorValues, centroidTemp, mergeState, calculatedGlobalCentroid ); + numCentroids = centroidAssignments.numCentroids(); + success = true; } finally { if (success == false && centroidTempName != null) { @@ -311,21 +295,28 @@ public final void mergeOneField(FieldInfo fieldInfo, MergeState mergeState) thro CodecUtil.writeFooter(centroidTemp); IOUtils.close(centroidTemp); centroidOffset = ivfCentroids.alignFilePointer(Float.BYTES); - try (IndexInput centroidInput = mergeState.segmentInfo.dir.openInput(centroidTempName, IOContext.DEFAULT)) { - ivfCentroids.copyBytes(centroidInput, centroidInput.length() - CodecUtil.footerLength()); + try (IndexInput centroidsInput = mergeState.segmentInfo.dir.openInput(centroidTempName, IOContext.DEFAULT)) { + ivfCentroids.copyBytes(centroidsInput, centroidsInput.length() - CodecUtil.footerLength()); centroidLength = ivfCentroids.getFilePointer() - centroidOffset; - centroidAssignmentScorer = createCentroidScorer(centroidInput, numCentroids, fieldInfo, calculatedGlobalCentroid); - assert centroidAssignmentScorer.size() == numCentroids; + + CentroidSupplier centroidSupplier = createCentroidSupplier( + centroidsInput, + numCentroids, + fieldInfo, + calculatedGlobalCentroid + ); + // build a float vector values with random access // build centroids final long[] offsets = buildAndWritePostingsLists( fieldInfo, - centroidAssignmentScorer, + centroidSupplier, floatVectorValues, ivfClusters, - mergeState + mergeState.infoStream, + centroidAssignments.assignmentsByCluster() ); - assert offsets.length == centroidAssignmentScorer.size(); + assert offsets.length == centroidSupplier.size(); writeMeta(fieldInfo, centroidOffset, centroidLength, offsets, calculatedGlobalCentroid); } } finally { @@ -453,8 +444,8 @@ public final long ramBytesUsed() { private record FieldWriter(FieldInfo fieldInfo, FlatFieldVectorsWriter delegate) {} - interface CentroidAssignmentScorer { - CentroidAssignmentScorer EMPTY = new CentroidAssignmentScorer() { + interface CentroidSupplier { + CentroidSupplier EMPTY = new CentroidSupplier() { @Override public int size() { return 0; @@ -464,24 +455,29 @@ public int size() { public float[] centroid(int centroidOrdinal) { throw new IllegalStateException("No centroids"); } - - @Override - public float score(int centroidOrdinal) { - throw new IllegalStateException("No centroids"); - } - - @Override - public void setScoringVector(float[] vector) { - throw new IllegalStateException("No centroids"); - } }; int size(); float[] centroid(int centroidOrdinal) throws IOException; + } - void setScoringVector(float[] vector); + // TODO throw away rawCentroids + static class OnHeapCentroidSupplier implements CentroidSupplier { + private final float[][] centroids; - float score(int centroidOrdinal) throws IOException; + OnHeapCentroidSupplier(float[][] centroids) { + this.centroids = centroids; + } + + @Override + public int size() { + return centroids.length; + } + + @Override + public float[] centroid(int centroidOrdinal) throws IOException { + return centroids[centroidOrdinal]; + } } } diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/KMeans.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/KMeans.java deleted file mode 100644 index 715791c5cbb54..0000000000000 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/KMeans.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * @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) 2025 Elasticsearch B.V. - */ -package org.elasticsearch.index.codec.vectors; - -import org.apache.lucene.index.FloatVectorValues; -import org.apache.lucene.index.VectorSimilarityFunction; -import org.apache.lucene.internal.hppc.IntArrayList; -import org.apache.lucene.internal.hppc.IntObjectHashMap; -import org.apache.lucene.util.ArrayUtil; -import org.apache.lucene.util.VectorUtil; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Random; -import java.util.Set; - -import static org.elasticsearch.index.codec.vectors.SampleReader.createSampleReader; - -/** KMeans clustering algorithm for vectors */ -class KMeans { - public static final int DEFAULT_RESTARTS = 1; - public static final int DEFAULT_ITRS = 10; - public static final int DEFAULT_SAMPLE_VECTORS_PER_CENTROID = 128; - - private static final float EPS = 1f / 1024f; - private final FloatVectorValues vectors; - private final int numVectors; - private final int numCentroids; - private final Random random; - private final KmeansInitializationMethod initializationMethod; - private final float[][] initCentroids; - private final int restarts; - private final int iters; - - /** - * Cluster vectors into a given number of clusters - * - * @param vectors float vectors - * @param similarityFunction vector similarity function. For COSINE similarity, vectors must be - * normalized. - * @param numClusters number of cluster to cluster vector into - * @return results of clustering: produced centroids and for each vector its centroid - * @throws IOException when if there is an error accessing vectors - */ - static Results cluster(FloatVectorValues vectors, VectorSimilarityFunction similarityFunction, int numClusters) throws IOException { - return cluster( - vectors, - numClusters, - true, - 42L, - KmeansInitializationMethod.PLUS_PLUS, - null, - similarityFunction == VectorSimilarityFunction.COSINE, - DEFAULT_RESTARTS, - DEFAULT_ITRS, - DEFAULT_SAMPLE_VECTORS_PER_CENTROID * numClusters - ); - } - - /** - * Expert: Cluster vectors into a given number of clusters - * - * @param vectors float vectors - * @param numClusters number of cluster to cluster vector into - * @param assignCentroidsToVectors if {@code true} assign centroids for all vectors. Centroids are - * computed on a sample of vectors. If this parameter is {@code true}, in results also return - * for all vectors what centroids they belong to. - * @param seed random seed - * @param initializationMethod Kmeans initialization method - * @param initCentroids initial centroids, if not {@code null} utilize as initial centroids for - * the given initialization method - * @param normalizeCenters for cosine distance, set to true, to use spherical k-means where - * centers are normalized - * @param restarts how many times to run Kmeans algorithm - * @param iters how many iterations to do within a single run - * @param sampleSize sample size to select from all vectors on which to run Kmeans algorithm - * @return results of clustering: produced centroids and if {@code assignCentroidsToVectors == - * true} also for each vector its centroid - * @throws IOException if there is error accessing vectors - */ - static Results cluster( - FloatVectorValues vectors, - int numClusters, - boolean assignCentroidsToVectors, - long seed, - KmeansInitializationMethod initializationMethod, - float[][] initCentroids, - boolean normalizeCenters, - int restarts, - int iters, - int sampleSize - ) throws IOException { - if (vectors.size() == 0) { - return null; - } - // adjust sampleSize and numClusters - sampleSize = Math.max(sampleSize, 100 * numClusters); - if (sampleSize > vectors.size()) { - sampleSize = vectors.size(); - // Decrease the number of clusters if needed - int maxNumClusters = Math.max(1, sampleSize / 100); - numClusters = Math.min(numClusters, maxNumClusters); - } - - Random random = new Random(seed); - float[][] centroids; - if (numClusters == 1) { - centroids = new float[1][vectors.dimension()]; - for (int i = 0; i < vectors.size(); i++) { - float[] vector = vectors.vectorValue(i); - for (int dim = 0; dim < vector.length; dim++) { - centroids[0][dim] += vector[dim]; - } - } - for (int dim = 0; dim < centroids[0].length; dim++) { - centroids[0][dim] /= vectors.size(); - } - } else { - FloatVectorValues sampleVectors = vectors.size() <= sampleSize ? vectors : createSampleReader(vectors, sampleSize, seed); - KMeans kmeans = new KMeans(sampleVectors, numClusters, random, initializationMethod, initCentroids, restarts, iters); - centroids = kmeans.computeCentroids(normalizeCenters); - } - - int[] vectorCentroids = null; - int[] centroidSize = null; - // Assign each vector to the nearest centroid and update the centres - if (assignCentroidsToVectors) { - vectorCentroids = new int[vectors.size()]; - centroidSize = new int[centroids.length]; - assignCentroids(random, vectorCentroids, centroidSize, vectors, centroids); - } - if (normalizeCenters) { - for (float[] centroid : centroids) { - VectorUtil.l2normalize(centroid, false); - } - } - return new Results(centroids, centroidSize, vectorCentroids); - } - - private static void assignCentroids( - Random random, - int[] docCentroids, - int[] centroidSize, - FloatVectorValues vectors, - float[][] centroids - ) throws IOException { - short numCentroids = (short) centroids.length; - assert Arrays.stream(centroidSize).allMatch(size -> size == 0); - for (int docID = 0; docID < vectors.size(); docID++) { - float[] vector = vectors.vectorValue(docID); - short bestCentroid = 0; - if (numCentroids > 1) { - float minSquaredDist = Float.MAX_VALUE; - for (short c = 0; c < numCentroids; c++) { - // TODO: replace with RandomVectorScorer::score possible on quantized vectors - float squareDist = VectorUtil.squareDistance(centroids[c], vector); - if (squareDist < minSquaredDist) { - bestCentroid = c; - minSquaredDist = squareDist; - } - } - } - centroidSize[bestCentroid] += 1; - docCentroids[docID] = bestCentroid; - } - - IntArrayList unassignedCentroids = new IntArrayList(); - for (int c = 0; c < numCentroids; c++) { - if (centroidSize[c] == 0) { - unassignedCentroids.add(c); - } - } - if (unassignedCentroids.size() > 0) { - throwAwayAndSplitCentroids(random, vectors, centroids, docCentroids, centroidSize, unassignedCentroids); - } - assert Arrays.stream(centroidSize).sum() == vectors.size(); - } - - private final float[] kmeansPlusPlusScratch; - - KMeans( - FloatVectorValues vectors, - int numCentroids, - Random random, - KmeansInitializationMethod initializationMethod, - float[][] initCentroids, - int restarts, - int iters - ) { - this.vectors = vectors; - this.numVectors = vectors.size(); - this.numCentroids = numCentroids; - this.random = random; - this.initializationMethod = initializationMethod; - this.restarts = restarts; - this.iters = iters; - this.initCentroids = initCentroids; - this.kmeansPlusPlusScratch = initializationMethod == KmeansInitializationMethod.PLUS_PLUS ? new float[numVectors] : null; - } - - float[][] computeCentroids(boolean normalizeCenters) throws IOException { - // TODO can we make this off-heap, or reusable? This could be a big array - int[] vectorCentroids = new int[numVectors]; - double minSquaredDist = Double.MAX_VALUE; - double squaredDist = 0; - float[][] bestCentroids = null; - float[][] centroids = new float[numCentroids][vectors.dimension()]; - int restarts = this.restarts; - int numInitializedCentroids = 0; - // The user has given us a solid number of centroids to start of with, so skip restarts, fill in - // where we can, and refine - if (initCentroids != null && initCentroids.length > numCentroids / 2) { - int i = 0; - for (; i < Math.min(numCentroids, initCentroids.length); i++) { - System.arraycopy(initCentroids[i], 0, centroids[i], 0, initCentroids[i].length); - } - numInitializedCentroids = i; - restarts = 1; - } - - for (int restart = 0; restart < restarts; restart++) { - switch (initializationMethod) { - case FORGY -> initializeForgy(centroids, numInitializedCentroids); - case RESERVOIR_SAMPLING -> initializeReservoirSampling(centroids, numInitializedCentroids); - case PLUS_PLUS -> initializePlusPlus(centroids, numInitializedCentroids); - } - double prevSquaredDist = Double.MAX_VALUE; - int[] centroidSize = new int[centroids.length]; - for (int iter = 0; iter < iters; iter++) { - squaredDist = runKMeansStep(centroids, centroidSize, vectorCentroids, normalizeCenters); - // Check for convergence - if (prevSquaredDist <= (squaredDist + 1e-6)) { - break; - } - Arrays.fill(centroidSize, 0); - prevSquaredDist = squaredDist; - } - if (squaredDist < minSquaredDist) { - minSquaredDist = squaredDist; - // Copy out the best centroid as it might be overwritten by the next restart - bestCentroids = new float[centroids.length][]; - for (int i = 0; i < centroids.length; i++) { - bestCentroids[i] = ArrayUtil.copyArray(centroids[i]); - } - } - } - return bestCentroids; - } - - /** - * Initialize centroids using Forgy method: randomly select numCentroids vectors for initial - * centroids - */ - private void initializeForgy(float[][] initialCentroids, int fromCentroid) throws IOException { - if (fromCentroid >= numCentroids) { - return; - } - int numCentroids = this.numCentroids - fromCentroid; - Set selection = new HashSet<>(); - while (selection.size() < numCentroids) { - selection.add(random.nextInt(numVectors)); - } - int i = 0; - for (Integer selectedIdx : selection) { - float[] vector = vectors.vectorValue(selectedIdx); - System.arraycopy(vector, 0, initialCentroids[fromCentroid + i++], 0, vector.length); - } - } - - /** Initialize centroids using a reservoir sampling method */ - private void initializeReservoirSampling(float[][] initialCentroids, int fromCentroid) throws IOException { - if (fromCentroid >= numCentroids) { - return; - } - int numCentroids = this.numCentroids - fromCentroid; - for (int index = 0; index < numVectors; index++) { - float[] vector = vectors.vectorValue(index); - if (index < numCentroids) { - System.arraycopy(vector, 0, initialCentroids[index + fromCentroid], 0, vector.length); - } else if (random.nextDouble() < numCentroids * (1.0 / index)) { - int c = random.nextInt(numCentroids); - System.arraycopy(vector, 0, initialCentroids[c + fromCentroid], 0, vector.length); - } - } - } - - /** Initialize centroids using Kmeans++ method */ - private void initializePlusPlus(float[][] initialCentroids, int fromCentroid) throws IOException { - if (fromCentroid >= numCentroids) { - return; - } - // Choose the first centroid uniformly at random - int firstIndex = random.nextInt(numVectors); - float[] value = vectors.vectorValue(firstIndex); - System.arraycopy(value, 0, initialCentroids[fromCentroid], 0, value.length); - - // Store distances of each point to the nearest centroid - Arrays.fill(kmeansPlusPlusScratch, Float.MAX_VALUE); - - // Step 2 and 3: Select remaining centroids - for (int i = fromCentroid + 1; i < numCentroids; i++) { - // Update distances with the new centroid - double totalSum = 0; - for (int j = 0; j < numVectors; j++) { - // TODO: replace with RandomVectorScorer::score possible on quantized vectors - float dist = VectorUtil.squareDistance(vectors.vectorValue(j), initialCentroids[i - 1]); - if (dist < kmeansPlusPlusScratch[j]) { - kmeansPlusPlusScratch[j] = dist; - } - totalSum += kmeansPlusPlusScratch[j]; - } - - // Randomly select next centroid - double r = totalSum * random.nextDouble(); - double cumulativeSum = 0; - int nextCentroidIndex = 0; - for (int j = 0; j < numVectors; j++) { - cumulativeSum += kmeansPlusPlusScratch[j]; - if (cumulativeSum >= r && kmeansPlusPlusScratch[j] > 0) { - nextCentroidIndex = j; - break; - } - } - // Update centroid - value = vectors.vectorValue(nextCentroidIndex); - System.arraycopy(value, 0, initialCentroids[i], 0, value.length); - } - } - - /** - * Run kmeans step - * - * @param centroids centroids, new calculated centroids are written here - * @param docCentroids for each document which centroid it belongs to, results will be written - * here - * @param normalizeCentroids if centroids should be normalized; used for cosine similarity only - * @throws IOException if there is an error accessing vector values - */ - private double runKMeansStep(float[][] centroids, int[] centroidSize, int[] docCentroids, boolean normalizeCentroids) - throws IOException { - short numCentroids = (short) centroids.length; - assert Arrays.stream(centroidSize).allMatch(size -> size == 0); - float[][] newCentroids = new float[numCentroids][centroids[0].length]; - - double sumSquaredDist = 0; - for (int docID = 0; docID < vectors.size(); docID++) { - float[] vector = vectors.vectorValue(docID); - short bestCentroid = 0; - if (numCentroids > 1) { - float minSquaredDist = Float.MAX_VALUE; - for (short c = 0; c < numCentroids; c++) { - // TODO: replace with RandomVectorScorer::score possible on quantized vectors - float squareDist = VectorUtil.squareDistance(centroids[c], vector); - if (squareDist < minSquaredDist) { - bestCentroid = c; - minSquaredDist = squareDist; - } - } - sumSquaredDist += minSquaredDist; - } - - centroidSize[bestCentroid] += 1; - for (int dim = 0; dim < vector.length; dim++) { - newCentroids[bestCentroid][dim] += vector[dim]; - } - docCentroids[docID] = bestCentroid; - } - - IntArrayList unassignedCentroids = new IntArrayList(); - for (int c = 0; c < numCentroids; c++) { - if (centroidSize[c] > 0) { - for (int dim = 0; dim < newCentroids[c].length; dim++) { - centroids[c][dim] = newCentroids[c][dim] / centroidSize[c]; - } - } else { - unassignedCentroids.add(c); - } - } - if (unassignedCentroids.size() > 0) { - throwAwayAndSplitCentroids(random, vectors, centroids, docCentroids, centroidSize, unassignedCentroids); - } - if (normalizeCentroids) { - for (float[] centroid : centroids) { - VectorUtil.l2normalize(centroid, false); - } - } - assert Arrays.stream(centroidSize).sum() == vectors.size(); - return sumSquaredDist; - } - - static void throwAwayAndSplitCentroids( - Random random, - FloatVectorValues vectors, - float[][] centroids, - int[] docCentroids, - int[] centroidSize, - IntArrayList unassignedCentroidsIdxs - ) throws IOException { - IntObjectHashMap splitCentroids = new IntObjectHashMap<>(unassignedCentroidsIdxs.size()); - // used for splitting logic - int[] splitSizes = Arrays.copyOf(centroidSize, centroidSize.length); - // FAISS style algorithm for splitting - for (int i = 0; i < unassignedCentroidsIdxs.size(); i++) { - int toSplit; - for (toSplit = 0; true; toSplit = (toSplit + 1) % centroids.length) { - /* probability to pick this cluster for split */ - double p = (splitSizes[toSplit] - 1.0) / (float) (docCentroids.length - centroids.length); - float r = random.nextFloat(); - if (r < p) { - break; /* found our cluster to be split */ - } - } - int unassignedCentroidIdx = unassignedCentroidsIdxs.get(i); - // keep track of those that are split, this way we reassign docCentroids and fix up true size - // & centroids - splitCentroids.getOrDefault(toSplit, new IntArrayList()).add(unassignedCentroidIdx); - System.arraycopy(centroids[toSplit], 0, centroids[unassignedCentroidIdx], 0, centroids[unassignedCentroidIdx].length); - for (int dim = 0; dim < centroids[unassignedCentroidIdx].length; dim++) { - if (dim % 2 == 0) { - centroids[unassignedCentroidIdx][dim] *= (1 + EPS); - centroids[toSplit][dim] *= (1 - EPS); - } else { - centroids[unassignedCentroidIdx][dim] *= (1 - EPS); - centroids[toSplit][dim] *= (1 + EPS); - } - } - splitSizes[unassignedCentroidIdx] = splitSizes[toSplit] / 2; - splitSizes[toSplit] -= splitSizes[unassignedCentroidIdx]; - } - // now we need to reassign docCentroids and fix up true size & centroids - for (int i = 0; i < docCentroids.length; i++) { - int docCentroid = docCentroids[i]; - IntArrayList split = splitCentroids.get(docCentroid); - if (split != null) { - // we need to reassign this doc - int bestCentroid = docCentroid; - float bestDist = VectorUtil.squareDistance(centroids[docCentroid], vectors.vectorValue(i)); - for (int j = 0; j < split.size(); j++) { - int newCentroid = split.get(j); - float dist = VectorUtil.squareDistance(centroids[newCentroid], vectors.vectorValue(i)); - if (dist < bestDist) { - bestCentroid = newCentroid; - bestDist = dist; - } - } - if (bestCentroid != docCentroid) { - // we need to update the centroid size - centroidSize[docCentroid]--; - centroidSize[bestCentroid]++; - docCentroids[i] = (short) bestCentroid; - // we need to update the old and new centroid accounting for size as well - for (int dim = 0; dim < centroids[docCentroid].length; dim++) { - centroids[docCentroid][dim] -= vectors.vectorValue(i)[dim] / centroidSize[docCentroid]; - centroids[bestCentroid][dim] += vectors.vectorValue(i)[dim] / centroidSize[bestCentroid]; - } - } - } - } - } - - /** Kmeans initialization methods */ - public enum KmeansInitializationMethod { - FORGY, - RESERVOIR_SAMPLING, - PLUS_PLUS - } - - /** - * Results of KMeans clustering - * - * @param centroids the produced centroids - * @param centroidsSize for each centroid how many vectors belong to it - * @param vectorCentroids for each vector which centroid it belongs to - */ - public record Results(float[][] centroids, int[] centroidsSize, int[] vectorCentroids) {} -} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/FloatVectorValuesSlice.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/FloatVectorValuesSlice.java new file mode 100644 index 0000000000000..6da6ff196e93e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/FloatVectorValuesSlice.java @@ -0,0 +1,52 @@ +/* + * 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.index.codec.vectors.cluster; + +import org.apache.lucene.index.FloatVectorValues; + +import java.io.IOException; + +class FloatVectorValuesSlice extends FloatVectorValues { + + private final FloatVectorValues allValues; + private final int[] slice; + + FloatVectorValuesSlice(FloatVectorValues allValues, int[] slice) { + assert slice != null; + assert slice.length <= allValues.size(); + this.allValues = allValues; + this.slice = slice; + } + + @Override + public float[] vectorValue(int ord) throws IOException { + return this.allValues.vectorValue(this.slice[ord]); + } + + @Override + public int dimension() { + return this.allValues.dimension(); + } + + @Override + public int size() { + return slice.length; + } + + @Override + public int ordToDoc(int ord) { + return this.slice[ord]; + } + + @Override + public FloatVectorValues copy() throws IOException { + return new FloatVectorValuesSlice(this.allValues.copy(), this.slice); + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeans.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeans.java new file mode 100644 index 0000000000000..fdd02a0cf752a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeans.java @@ -0,0 +1,197 @@ +/* + * 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.index.codec.vectors.cluster; + +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.util.VectorUtil; + +import java.io.IOException; + +/** + * An implementation of the hierarchical k-means algorithm that better partitions data than naive k-means + */ +public class HierarchicalKMeans { + + static final int MAXK = 128; + static final int MAX_ITERATIONS_DEFAULT = 6; + static final int SAMPLES_PER_CLUSTER_DEFAULT = 256; + static final float DEFAULT_SOAR_LAMBDA = 1.0f; + + final int dimension; + final int maxIterations; + final int samplesPerCluster; + final int clustersPerNeighborhood; + final float soarLambda; + + public HierarchicalKMeans(int dimension) { + this(dimension, MAX_ITERATIONS_DEFAULT, SAMPLES_PER_CLUSTER_DEFAULT, MAXK, DEFAULT_SOAR_LAMBDA); + } + + HierarchicalKMeans(int dimension, int maxIterations, int samplesPerCluster, int clustersPerNeighborhood, float soarLambda) { + this.dimension = dimension; + this.maxIterations = maxIterations; + this.samplesPerCluster = samplesPerCluster; + this.clustersPerNeighborhood = clustersPerNeighborhood; + this.soarLambda = soarLambda; + } + + /** + * clusters or moreso partitions the set of vectors by starting with a rough number of partitions and then recursively refining those + * lastly a pass is made to adjust nearby neighborhoods and add an extra assignment per vector to nearby neighborhoods + * + * @param vectors the vectors to cluster + * @param targetSize the rough number of vectors that should be attached to a cluster + * @return the centroids and the vectors assignments and SOAR (spilled from nearby neighborhoods) assignments + * @throws IOException is thrown if vectors is inaccessible + */ + public KMeansResult cluster(FloatVectorValues vectors, int targetSize) throws IOException { + + if (vectors.size() == 0) { + return new KMeansIntermediate(); + } + + // if we have a small number of vectors pick one and output that as the centroid + if (vectors.size() <= targetSize) { + float[] centroid = new float[dimension]; + System.arraycopy(vectors.vectorValue(0), 0, centroid, 0, dimension); + return new KMeansIntermediate(new float[][] { centroid }, new int[vectors.size()]); + } + + // partition the space + KMeansIntermediate kMeansIntermediate = clusterAndSplit(vectors, targetSize); + if (kMeansIntermediate.centroids().length > 1 && kMeansIntermediate.centroids().length < vectors.size()) { + float f = Math.min((float) samplesPerCluster / targetSize, 1.0f); + int localSampleSize = (int) (f * vectors.size()); + KMeansLocal kMeansLocal = new KMeansLocal(localSampleSize, maxIterations, clustersPerNeighborhood, DEFAULT_SOAR_LAMBDA); + kMeansLocal.cluster(vectors, kMeansIntermediate, true); + } + + return kMeansIntermediate; + } + + KMeansIntermediate clusterAndSplit(final FloatVectorValues vectors, final int targetSize) throws IOException { + if (vectors.size() <= targetSize) { + return new KMeansIntermediate(); + } + + int k = Math.clamp((int) ((vectors.size() + targetSize / 2.0f) / (float) targetSize), 2, MAXK); + int m = Math.min(k * samplesPerCluster, vectors.size()); + + // TODO: instead of creating a sub-cluster assignments reuse the parent array each time + int[] assignments = new int[vectors.size()]; + + KMeansLocal kmeans = new KMeansLocal(m, maxIterations); + float[][] centroids = KMeansLocal.pickInitialCentroids(vectors, k); + KMeansIntermediate kMeansIntermediate = new KMeansIntermediate(centroids); + kmeans.cluster(vectors, kMeansIntermediate); + + // TODO: consider adding cluster size counts to the kmeans algo + // handle assignment here so we can track distance and cluster size + int[] centroidVectorCount = new int[centroids.length]; + float[][] nextCentroids = new float[centroids.length][dimension]; + for (int i = 0; i < vectors.size(); i++) { + float smallest = Float.MAX_VALUE; + int centroidIdx = -1; + float[] vector = vectors.vectorValue(i); + for (int j = 0; j < centroids.length; j++) { + float[] centroid = centroids[j]; + float d = VectorUtil.squareDistance(vector, centroid); + if (d < smallest) { + smallest = d; + centroidIdx = j; + } + } + centroidVectorCount[centroidIdx]++; + for (int j = 0; j < dimension; j++) { + nextCentroids[centroidIdx][j] += vector[j]; + } + assignments[i] = centroidIdx; + } + + // update centroids based on assignments of all vectors + for (int i = 0; i < centroids.length; i++) { + if (centroidVectorCount[i] > 0) { + for (int j = 0; j < dimension; j++) { + centroids[i][j] = nextCentroids[i][j] / centroidVectorCount[i]; + } + } + } + + int effectiveK = 0; + for (int i = 0; i < centroidVectorCount.length; i++) { + if (centroidVectorCount[i] > 0) { + effectiveK++; + } + } + + kMeansIntermediate = new KMeansIntermediate(centroids, assignments, vectors::ordToDoc); + + if (effectiveK == 1) { + return kMeansIntermediate; + } + + for (int c = 0; c < centroidVectorCount.length; c++) { + // Recurse for each cluster which is larger than targetSize + // Give ourselves 30% margin for the target size + if (100 * centroidVectorCount[c] > 134 * targetSize) { + FloatVectorValues sample = createClusterSlice(centroidVectorCount[c], c, vectors, assignments); + + // TODO: consider iterative here instead of recursive + // recursive call to build out the sub partitions around this centroid c + // subsequently reconcile and flatten the space of all centroids and assignments into one structure we can return + updateAssignmentsWithRecursiveSplit(kMeansIntermediate, c, clusterAndSplit(sample, targetSize)); + } + } + + return kMeansIntermediate; + } + + static FloatVectorValues createClusterSlice(int clusterSize, int cluster, FloatVectorValues vectors, int[] assignments) { + int[] slice = new int[clusterSize]; + int idx = 0; + for (int i = 0; i < assignments.length; i++) { + if (assignments[i] == cluster) { + slice[idx] = i; + idx++; + } + } + + return new FloatVectorValuesSlice(vectors, slice); + } + + void updateAssignmentsWithRecursiveSplit(KMeansIntermediate current, int cluster, KMeansIntermediate subPartitions) { + int orgCentroidsSize = current.centroids().length; + int newCentroidsSize = current.centroids().length + subPartitions.centroids().length - 1; + + // update based on the outcomes from the split clusters recursion + if (subPartitions.centroids().length > 1) { + float[][] newCentroids = new float[newCentroidsSize][dimension]; + System.arraycopy(current.centroids(), 0, newCentroids, 0, current.centroids().length); + + // replace the original cluster + int origCentroidOrd = 0; + newCentroids[cluster] = subPartitions.centroids()[0]; + + // append the remainder + System.arraycopy(subPartitions.centroids(), 1, newCentroids, current.centroids().length, subPartitions.centroids().length - 1); + + current.setCentroids(newCentroids); + + for (int i = 0; i < subPartitions.assignments().length; i++) { + // this is a new centroid that was added, and so we'll need to remap it + if (subPartitions.assignments()[i] != origCentroidOrd) { + int parentOrd = subPartitions.ordToDoc(i); + assert current.assignments()[parentOrd] == cluster; + current.assignments()[parentOrd] = subPartitions.assignments()[i] + orgCentroidsSize - 1; + } + } + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansIntermediate.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansIntermediate.java new file mode 100644 index 0000000000000..75caa5c7d3281 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansIntermediate.java @@ -0,0 +1,45 @@ +/* + * 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.index.codec.vectors.cluster; + +import org.apache.lucene.util.hnsw.IntToIntFunction; + +/** + * Intermediate object for clustering (partitioning) a set of vectors + */ +class KMeansIntermediate extends KMeansResult { + private final IntToIntFunction assignmentOrds; + + private KMeansIntermediate(float[][] centroids, int[] assignments, IntToIntFunction assignmentOrds, int[] soarAssignments) { + super(centroids, assignments, soarAssignments); + assert assignmentOrds != null; + this.assignmentOrds = assignmentOrds; + } + + KMeansIntermediate(float[][] centroids, int[] assignments, IntToIntFunction assignmentOrdinals) { + this(centroids, assignments, assignmentOrdinals, new int[0]); + } + + KMeansIntermediate() { + this(new float[0][0], new int[0], i -> i, new int[0]); + } + + KMeansIntermediate(float[][] centroids) { + this(centroids, new int[0], i -> i, new int[0]); + } + + KMeansIntermediate(float[][] centroids, int[] assignments) { + this(centroids, assignments, i -> i, new int[0]); + } + + public int ordToDoc(int ord) { + return assignmentOrds.apply(ord); + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocal.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocal.java new file mode 100644 index 0000000000000..415a082c5a2b1 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocal.java @@ -0,0 +1,306 @@ +/* + * 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.index.codec.vectors.cluster; + +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.util.VectorUtil; +import org.elasticsearch.simdvec.ESVectorUtil; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * k-means implementation specific to the needs of the {@link HierarchicalKMeans} algorithm that deals specifically + * with finalizing nearby pre-established clusters and generate + * SOAR assignments + */ +class KMeansLocal { + + final int sampleSize; + final int maxIterations; + final int clustersPerNeighborhood; + final float soarLambda; + + KMeansLocal(int sampleSize, int maxIterations, int clustersPerNeighborhood, float soarLambda) { + this.sampleSize = sampleSize; + this.maxIterations = maxIterations; + this.clustersPerNeighborhood = clustersPerNeighborhood; + this.soarLambda = soarLambda; + } + + KMeansLocal(int sampleSize, int maxIterations) { + this(sampleSize, maxIterations, -1, -1f); + } + + /** + * uses a Reservoir Sampling approach to picking the initial centroids which are subsequently expected + * to be used by a clustering algorithm + * + * @param vectors used to pick an initial set of random centroids + * @param centroidCount the total number of centroids to pick + * @return randomly selected centroids that are the min of centroidCount and sampleSize + * @throws IOException is thrown if vectors is inaccessible + */ + static float[][] pickInitialCentroids(FloatVectorValues vectors, int centroidCount) throws IOException { + Random random = new Random(42L); + int centroidsSize = Math.min(vectors.size(), centroidCount); + float[][] centroids = new float[centroidsSize][vectors.dimension()]; + for (int i = 0; i < vectors.size(); i++) { + float[] vector; + if (i < centroidCount) { + vector = vectors.vectorValue(i); + System.arraycopy(vector, 0, centroids[i], 0, vector.length); + } else if (random.nextDouble() < centroidCount * (1.0 / i)) { + int c = random.nextInt(centroidCount); + vector = vectors.vectorValue(i); + System.arraycopy(vector, 0, centroids[c], 0, vector.length); + } + } + return centroids; + } + + private boolean stepLloyd( + FloatVectorValues vectors, + float[][] centroids, + float[][] nextCentroids, + int[] assignments, + int sampleSize, + List neighborhoods + ) throws IOException { + boolean changed = false; + int dim = vectors.dimension(); + int[] centroidCounts = new int[centroids.length]; + + for (int i = 0; i < nextCentroids.length; i++) { + Arrays.fill(nextCentroids[i], 0.0f); + } + + for (int i = 0; i < sampleSize; i++) { + float[] vector = vectors.vectorValue(i); + int[] neighborOffsets = null; + int centroidIdx = -1; + if (neighborhoods != null) { + neighborOffsets = neighborhoods.get(assignments[i]); + centroidIdx = assignments[i]; + } + int bestCentroidOffset = getBestCentroidOffset(centroids, vector, centroidIdx, neighborOffsets); + if (assignments[i] != bestCentroidOffset) { + changed = true; + } + assignments[i] = bestCentroidOffset; + centroidCounts[bestCentroidOffset]++; + for (short d = 0; d < dim; d++) { + nextCentroids[bestCentroidOffset][d] += vector[d]; + } + } + + for (int clusterIdx = 0; clusterIdx < centroids.length; clusterIdx++) { + if (centroidCounts[clusterIdx] > 0) { + float countF = (float) centroidCounts[clusterIdx]; + for (short d = 0; d < dim; d++) { + centroids[clusterIdx][d] = nextCentroids[clusterIdx][d] / countF; + } + } + } + + return changed; + } + + int getBestCentroidOffset(float[][] centroids, float[] vector, int centroidIdx, int[] centroidOffsets) { + int bestCentroidOffset = centroidIdx; + float minDsq; + if (centroidIdx > 0 && centroidIdx < centroids.length) { + minDsq = VectorUtil.squareDistance(vector, centroids[centroidIdx]); + } else { + minDsq = Float.MAX_VALUE; + } + + int k = 0; + for (int j = 0; j < centroids.length; j++) { + if (centroidOffsets == null || j == centroidOffsets[k]) { + float dsq = VectorUtil.squareDistance(vector, centroids[j]); + if (dsq < minDsq) { + minDsq = dsq; + bestCentroidOffset = j; + } + } + } + return bestCentroidOffset; + } + + private void computeNeighborhoods(float[][] centers, List neighborhoods, int clustersPerNeighborhood) { + int k = neighborhoods.size(); + + if (k == 0 || clustersPerNeighborhood <= 0) { + return; + } + + List neighborQueues = new ArrayList<>(k); + for (int i = 0; i < k; i++) { + neighborQueues.add(new NeighborQueue(clustersPerNeighborhood, true)); + } + for (int i = 0; i < k - 1; i++) { + for (int j = i + 1; j < k; j++) { + float dsq = VectorUtil.squareDistance(centers[i], centers[j]); + neighborQueues.get(j).insertWithOverflow(i, dsq); + neighborQueues.get(i).insertWithOverflow(j, dsq); + } + } + + for (int i = 0; i < k; i++) { + NeighborQueue queue = neighborQueues.get(i); + int neighborCount = queue.size(); + int[] neighbors = new int[neighborCount]; + queue.consumeNodes(neighbors); + neighborhoods.set(i, neighbors); + } + } + + private int[] assignSpilled(FloatVectorValues vectors, List neighborhoods, float[][] centroids, int[] assignments) + throws IOException { + // SOAR uses an adjusted distance for assigning spilled documents which is + // given by: + // + // soar(x, c) = ||x - c||^2 + lambda * ((x - c_1)^t (x - c))^2 / ||x - c_1||^2 + // + // Here, x is the document, c is the nearest centroid, and c_1 is the first + // centroid the document was assigned to. The document is assigned to the + // cluster with the smallest soar(x, c). + + int[] spilledAssignments = new int[assignments.length]; + + float[] diffs = new float[vectors.dimension()]; + for (int i = 0; i < vectors.size(); i++) { + float[] vector = vectors.vectorValue(i); + + int currAssignment = assignments[i]; + float[] currentCentroid = centroids[currAssignment]; + for (short j = 0; j < vectors.dimension(); j++) { + float diff = vector[j] - currentCentroid[j]; + diffs[j] = diff; + } + + // TODO: cache these? + // float vectorCentroidDist = assignmentDistances[i]; + float vectorCentroidDist = VectorUtil.squareDistance(vector, currentCentroid); + + int bestAssignment = -1; + float minSoar = Float.MAX_VALUE; + assert neighborhoods.get(currAssignment) != null; + for (int neighbor : neighborhoods.get(currAssignment)) { + if (neighbor == currAssignment) { + continue; + } + float[] neighborCentroid = centroids[neighbor]; + float soar = distanceSoar(diffs, vector, neighborCentroid, vectorCentroidDist); + if (soar < minSoar) { + bestAssignment = neighbor; + minSoar = soar; + } + } + + spilledAssignments[i] = bestAssignment; + } + + return spilledAssignments; + } + + private float distanceSoar(float[] residual, float[] vector, float[] centroid, float rnorm) { + // TODO: combine these to be more efficient + float dsq = VectorUtil.squareDistance(vector, centroid); + float rproj = ESVectorUtil.soarResidual(vector, centroid, residual); + return dsq + soarLambda * rproj * rproj / rnorm; + } + + /** + * cluster using a lloyd k-means algorithm that is not neighbor aware + * + * @param vectors the vectors to cluster + * @param kMeansIntermediate the output object to populate which minimally includes centroids, + * but may include assignments and soar assignments as well; care should be taken in + * passing in a valid output object with a centroids array that is the size of centroids expected + * @throws IOException is thrown if vectors is inaccessible + */ + void cluster(FloatVectorValues vectors, KMeansIntermediate kMeansIntermediate) throws IOException { + cluster(vectors, kMeansIntermediate, false); + } + + /** + * cluster using a lloyd kmeans algorithm that also considers prior clustered neighborhoods when adjusting centroids + * this also is used to generate the neighborhood aware additional (SOAR) assignments + * + * @param vectors the vectors to cluster + * @param kMeansIntermediate the output object to populate which minimally includes centroids, + * the prior assignments of the given vectors; care should be taken in + * passing in a valid output object with a centroids array that is the size of centroids expected + * and assignments that are the same size as the vectors. The SOAR assignments are overwritten by this operation. + * @param neighborAware whether nearby neighboring centroids and their vectors should be used to update the centroid positions, + * implies SOAR assignments + * @throws IOException is thrown if vectors is inaccessible + */ + void cluster(FloatVectorValues vectors, KMeansIntermediate kMeansIntermediate, boolean neighborAware) throws IOException { + float[][] centroids = kMeansIntermediate.centroids(); + + List neighborhoods = null; + if (neighborAware) { + int k = centroids.length; + neighborhoods = new ArrayList<>(k); + for (int i = 0; i < k; ++i) { + neighborhoods.add(null); + } + computeNeighborhoods(centroids, neighborhoods, clustersPerNeighborhood); + } + cluster(vectors, kMeansIntermediate, neighborhoods); + if (neighborAware && clustersPerNeighborhood > 0) { + int[] assignments = kMeansIntermediate.assignments(); + assert assignments != null; + assert assignments.length == vectors.size(); + kMeansIntermediate.setSoarAssignments(assignSpilled(vectors, neighborhoods, centroids, assignments)); + } + } + + void cluster(FloatVectorValues vectors, KMeansIntermediate kMeansIntermediate, List neighborhoods) throws IOException { + float[][] centroids = kMeansIntermediate.centroids(); + int k = centroids.length; + int n = vectors.size(); + + if (k == 1 || k >= n) { + return; + } + + int[] assignments = new int[n]; + float[][] nextCentroids = new float[centroids.length][vectors.dimension()]; + for (int i = 0; i < maxIterations; i++) { + if (stepLloyd(vectors, centroids, nextCentroids, assignments, sampleSize, neighborhoods) == false) { + break; + } + } + stepLloyd(vectors, centroids, nextCentroids, assignments, vectors.size(), neighborhoods); + } + + /** + * helper that calls {@link KMeansLocal#cluster(FloatVectorValues, KMeansIntermediate)} given a set of initialized centroids, + * this call is not neighbor aware + * + * @param vectors the vectors to cluster + * @param centroids the initialized centroids to be shifted using k-means + * @param sampleSize the subset of vectors to use when shifting centroids + * @param maxIterations the max iterations to shift centroids + */ + public static void cluster(FloatVectorValues vectors, float[][] centroids, int sampleSize, int maxIterations) throws IOException { + KMeansIntermediate kMeansIntermediate = new KMeansIntermediate(centroids); + KMeansLocal kMeans = new KMeansLocal(sampleSize, maxIterations); + kMeans.cluster(vectors, kMeansIntermediate); + } + +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansResult.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansResult.java new file mode 100644 index 0000000000000..5c2f4afb03f1a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/KMeansResult.java @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", 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.index.codec.vectors.cluster; + +/** + * Output object for clustering (partitioning) a set of vectors + */ +public class KMeansResult { + private float[][] centroids; + private final int[] assignments; + private int[] soarAssignments; + + KMeansResult(float[][] centroids, int[] assignments, int[] soarAssignments) { + assert centroids != null; + assert assignments != null; + assert soarAssignments != null; + this.centroids = centroids; + this.assignments = assignments; + this.soarAssignments = soarAssignments; + } + + public float[][] centroids() { + return centroids; + } + + void setCentroids(float[][] centroids) { + this.centroids = centroids; + } + + public int[] assignments() { + return assignments; + } + + void setSoarAssignments(int[] soarAssignments) { + this.soarAssignments = soarAssignments; + } + + public int[] soarAssignments() { + return soarAssignments; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/NeighborQueue.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/NeighborQueue.java similarity index 98% rename from server/src/main/java/org/elasticsearch/index/codec/vectors/NeighborQueue.java rename to server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/NeighborQueue.java index f27e85d46cddb..48aa3c5004843 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/NeighborQueue.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/cluster/NeighborQueue.java @@ -14,10 +14,10 @@ * 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) 2025 Elasticsearch B.V. */ - -package org.elasticsearch.index.codec.vectors; +package org.elasticsearch.index.codec.vectors.cluster; import org.apache.lucene.util.LongHeap; import org.apache.lucene.util.NumericUtils; diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/KMeansTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/KMeansTests.java deleted file mode 100644 index 001847a521ba5..0000000000000 --- a/server/src/test/java/org/elasticsearch/index/codec/vectors/KMeansTests.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * @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) 2025 Elasticsearch B.V. - */ - -package org.elasticsearch.index.codec.vectors; - -import org.apache.lucene.index.FloatVectorValues; -import org.apache.lucene.index.VectorSimilarityFunction; -import org.elasticsearch.test.ESTestCase; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class KMeansTests extends ESTestCase { - - public void testKMeansAPI() throws IOException { - int nClusters = random().nextInt(1, 10); - int nVectors = random().nextInt(nClusters * 100, nClusters * 200); - int dims = random().nextInt(2, 20); - int randIdx = random().nextInt(VectorSimilarityFunction.values().length); - VectorSimilarityFunction similarityFunction = VectorSimilarityFunction.values()[randIdx]; - FloatVectorValues vectors = generateData(nVectors, dims, nClusters); - - // default case - { - KMeans.Results results = KMeans.cluster(vectors, similarityFunction, nClusters); - assertResults(results, nClusters, nVectors, true); - assertEquals(nClusters, results.centroids().length); - assertEquals(nClusters, results.centroidsSize().length); - assertEquals(nVectors, results.vectorCentroids().length); - } - // expert case - { - boolean assignCentroidsToVectors = random().nextBoolean(); - int randIdx2 = random().nextInt(KMeans.KmeansInitializationMethod.values().length); - KMeans.KmeansInitializationMethod initializationMethod = KMeans.KmeansInitializationMethod.values()[randIdx2]; - int restarts = random().nextInt(1, 6); - int iters = random().nextInt(1, 10); - int sampleSize = random().nextInt(10, nVectors * 2); - - KMeans.Results results = KMeans.cluster( - vectors, - nClusters, - assignCentroidsToVectors, - random().nextLong(), - initializationMethod, - null, - similarityFunction == VectorSimilarityFunction.COSINE, - restarts, - iters, - sampleSize - ); - assertResults(results, nClusters, nVectors, assignCentroidsToVectors); - } - } - - private void assertResults(KMeans.Results results, int nClusters, int nVectors, boolean assignCentroidsToVectors) { - assertEquals(nClusters, results.centroids().length); - if (assignCentroidsToVectors) { - assertEquals(nClusters, results.centroidsSize().length); - assertEquals(nVectors, results.vectorCentroids().length); - int[] centroidsSize = new int[nClusters]; - for (int i = 0; i < nVectors; i++) { - centroidsSize[results.vectorCentroids()[i]]++; - } - assertArrayEquals(centroidsSize, results.centroidsSize()); - } else { - assertNull(results.vectorCentroids()); - } - } - - public void testKMeansSpecialCases() throws IOException { - { - // nClusters > nVectors - int nClusters = 20; - int nVectors = 10; - FloatVectorValues vectors = generateData(nVectors, 5, nClusters); - KMeans.Results results = KMeans.cluster(vectors, VectorSimilarityFunction.EUCLIDEAN, nClusters); - // assert that we get 1 centroid, as nClusters will be adjusted - assertEquals(1, results.centroids().length); - assertEquals(nVectors, results.vectorCentroids().length); - } - { - // small sample size - int sampleSize = 2; - int nClusters = 2; - int nVectors = 300; - FloatVectorValues vectors = generateData(nVectors, 5, nClusters); - KMeans.KmeansInitializationMethod initializationMethod = KMeans.KmeansInitializationMethod.PLUS_PLUS; - KMeans.Results results = KMeans.cluster( - vectors, - nClusters, - true, - random().nextLong(), - initializationMethod, - null, - false, - 1, - 2, - sampleSize - ); - assertResults(results, nClusters, nVectors, true); - } - } - - public void testKMeansSAllZero() throws IOException { - int nClusters = 10; - List vectors = new ArrayList<>(); - for (int i = 0; i < 1000; i++) { - float[] vector = new float[5]; - vectors.add(vector); - } - KMeans.Results results = KMeans.cluster(FloatVectorValues.fromFloats(vectors, 5), VectorSimilarityFunction.EUCLIDEAN, nClusters); - assertResults(results, nClusters, 1000, true); - } - - private static FloatVectorValues generateData(int nSamples, int nDims, int nClusters) { - List vectors = new ArrayList<>(nSamples); - float[][] centroids = new float[nClusters][nDims]; - // Generate random centroids - for (int i = 0; i < nClusters; i++) { - for (int j = 0; j < nDims; j++) { - centroids[i][j] = random().nextFloat() * 100; - } - } - // Generate data points around centroids - for (int i = 0; i < nSamples; i++) { - int cluster = random().nextInt(nClusters); - float[] vector = new float[nDims]; - for (int j = 0; j < nDims; j++) { - vector[j] = centroids[cluster][j] + random().nextFloat() * 10 - 5; - } - vectors.add(vector); - } - return FloatVectorValues.fromFloats(vectors, nDims); - } -} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeansTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeansTests.java new file mode 100644 index 0000000000000..4c481ca4a5f36 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/HierarchicalKMeansTests.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.index.codec.vectors.cluster; + +import org.apache.lucene.index.FloatVectorValues; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class HierarchicalKMeansTests extends ESTestCase { + + public void testHKmeans() throws IOException { + int nClusters = random().nextInt(1, 10); + int nVectors = random().nextInt(nClusters * 100, nClusters * 200); + int dims = random().nextInt(2, 20); + int sampleSize = random().nextInt(100, nVectors + 1); + int maxIterations = random().nextInt(0, 100); + int clustersPerNeighborhood = random().nextInt(0, 512); + float soarLambda = random().nextFloat(0.5f, 1.5f); + FloatVectorValues vectors = generateData(nVectors, dims, nClusters); + + int targetSize = (int) ((float) nVectors / (float) nClusters); + HierarchicalKMeans hkmeans = new HierarchicalKMeans(dims, maxIterations, sampleSize, clustersPerNeighborhood, soarLambda); + + KMeansResult result = hkmeans.cluster(vectors, targetSize); + + float[][] centroids = result.centroids(); + int[] assignments = result.assignments(); + int[] soarAssignments = result.soarAssignments(); + + assertEquals(nClusters, centroids.length, 6); + assertEquals(nVectors, assignments.length); + if (centroids.length > 1 && clustersPerNeighborhood > 0) { + assertEquals(nVectors, soarAssignments.length); + // verify no duplicates exist + for (int i = 0; i < assignments.length; i++) { + assert assignments[i] != soarAssignments[i]; + } + } + } + + private static FloatVectorValues generateData(int nSamples, int nDims, int nClusters) { + List vectors = new ArrayList<>(nSamples); + float[][] centroids = new float[nClusters][nDims]; + // Generate random centroids + for (int i = 0; i < nClusters; i++) { + for (int j = 0; j < nDims; j++) { + centroids[i][j] = random().nextFloat() * 100; + } + } + // Generate data points around centroids + for (int i = 0; i < nSamples; i++) { + int cluster = random().nextInt(nClusters); + float[] vector = new float[nDims]; + for (int j = 0; j < nDims; j++) { + vector[j] = centroids[cluster][j] + random().nextFloat() * 10 - 5; + } + vectors.add(vector); + } + return FloatVectorValues.fromFloats(vectors, nDims); + } +} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocalTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocalTests.java new file mode 100644 index 0000000000000..c0a0ca8341129 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/KMeansLocalTests.java @@ -0,0 +1,127 @@ +/* + * 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.index.codec.vectors.cluster; + +import org.apache.lucene.index.FloatVectorValues; +import org.apache.lucene.util.VectorUtil; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class KMeansLocalTests extends ESTestCase { + + public void testKMeansNeighbors() throws IOException { + int nClusters = random().nextInt(1, 10); + int nVectors = random().nextInt(nClusters * 100, nClusters * 200); + int dims = random().nextInt(2, 20); + int sampleSize = random().nextInt(100, nVectors + 1); + int maxIterations = random().nextInt(0, 100); + int clustersPerNeighborhood = random().nextInt(0, 512); + float soarLambda = random().nextFloat(0.5f, 1.5f); + FloatVectorValues vectors = generateData(nVectors, dims, nClusters); + + float[][] centroids = KMeansLocal.pickInitialCentroids(vectors, nClusters); + KMeansLocal.cluster(vectors, centroids, sampleSize, maxIterations); + + int[] assignments = new int[vectors.size()]; + int[] assignmentOrdinals = new int[vectors.size()]; + for (int i = 0; i < vectors.size(); i++) { + float minDist = Float.MAX_VALUE; + int ord = -1; + for (int j = 0; j < centroids.length; j++) { + float dist = VectorUtil.squareDistance(vectors.vectorValue(i), centroids[j]); + if (dist < minDist) { + minDist = dist; + ord = j; + } + } + assignments[i] = ord; + assignmentOrdinals[i] = i; + } + + KMeansIntermediate kMeansIntermediate = new KMeansIntermediate(centroids, assignments, i -> assignmentOrdinals[i]); + KMeansLocal kMeansLocal = new KMeansLocal(sampleSize, maxIterations, clustersPerNeighborhood, soarLambda); + kMeansLocal.cluster(vectors, kMeansIntermediate, true); + + assertEquals(nClusters, centroids.length); + assertNotNull(kMeansIntermediate.soarAssignments()); + } + + public void testKMeansNeighborsAllZero() throws IOException { + int nClusters = 10; + int maxIterations = 10; + int clustersPerNeighborhood = 128; + float soarLambda = 1.0f; + int nVectors = 1000; + List vectors = new ArrayList<>(); + for (int i = 0; i < nVectors; i++) { + float[] vector = new float[5]; + vectors.add(vector); + } + int sampleSize = vectors.size(); + FloatVectorValues fvv = FloatVectorValues.fromFloats(vectors, 5); + + float[][] centroids = KMeansLocal.pickInitialCentroids(fvv, nClusters); + KMeansLocal.cluster(fvv, centroids, sampleSize, maxIterations); + + int[] assignments = new int[vectors.size()]; + int[] assignmentOrdinals = new int[vectors.size()]; + for (int i = 0; i < vectors.size(); i++) { + float minDist = Float.MAX_VALUE; + int ord = -1; + for (int j = 0; j < centroids.length; j++) { + float dist = VectorUtil.squareDistance(fvv.vectorValue(i), centroids[j]); + if (dist < minDist) { + minDist = dist; + ord = j; + } + } + assignments[i] = ord; + assignmentOrdinals[i] = i; + } + + KMeansIntermediate kMeansIntermediate = new KMeansIntermediate(centroids, assignments, i -> assignmentOrdinals[i]); + KMeansLocal kMeansLocal = new KMeansLocal(sampleSize, maxIterations, clustersPerNeighborhood, soarLambda); + kMeansLocal.cluster(fvv, kMeansIntermediate, true); + + assertEquals(nClusters, centroids.length); + assertNotNull(kMeansIntermediate.soarAssignments()); + for (float[] centroid : centroids) { + for (float v : centroid) { + if (v > 0.0000001f) { + assertEquals(0.0f, v, 0.00000001f); + } + } + } + } + + private static FloatVectorValues generateData(int nSamples, int nDims, int nClusters) { + List vectors = new ArrayList<>(nSamples); + float[][] centroids = new float[nClusters][nDims]; + // Generate random centroids + for (int i = 0; i < nClusters; i++) { + for (int j = 0; j < nDims; j++) { + centroids[i][j] = random().nextFloat() * 100; + } + } + // Generate data points around centroids + for (int i = 0; i < nSamples; i++) { + int cluster = random().nextInt(nClusters); + float[] vector = new float[nDims]; + for (int j = 0; j < nDims; j++) { + vector[j] = centroids[cluster][j] + random().nextFloat() * 10 - 5; + } + vectors.add(vector); + } + return FloatVectorValues.fromFloats(vectors, nDims); + } +} diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/NeighborQueueTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/NeighborQueueTests.java similarity index 97% rename from server/src/test/java/org/elasticsearch/index/codec/vectors/NeighborQueueTests.java rename to server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/NeighborQueueTests.java index 7238f58d746dc..56c86b4ef6bc9 100644 --- a/server/src/test/java/org/elasticsearch/index/codec/vectors/NeighborQueueTests.java +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/cluster/NeighborQueueTests.java @@ -14,10 +14,10 @@ * 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) 2025 Elasticsearch B.V. + * + * Modifications copyright (C) 2024 Elasticsearch B.V. */ - -package org.elasticsearch.index.codec.vectors; +package org.elasticsearch.index.codec.vectors.cluster; import org.elasticsearch.test.ESTestCase; From 14a2956146eafb36c11b13662d4307b01dd3a9be Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Wed, 11 Jun 2025 07:51:45 +1000 Subject: [PATCH 047/102] Mute org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT test {lookup-join.EnrichLookupStatsBug ASYNC} #129228 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 1d15744cb2a94..f796f903f7d2b 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -519,6 +519,9 @@ tests: - class: org.elasticsearch.index.engine.ThreadPoolMergeExecutorServiceDiskSpaceTests method: testUnavailableBudgetBlocksNewMergeTasksFromStartingExecution issue: https://github.com/elastic/elasticsearch/issues/129148 +- class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT + method: test {lookup-join.EnrichLookupStatsBug ASYNC} + issue: https://github.com/elastic/elasticsearch/issues/129228 # Examples: # From 70cc42798836a892dfb50512f8e5b01c1e639b78 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Wed, 11 Jun 2025 07:51:52 +1000 Subject: [PATCH 048/102] Mute org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT test {lookup-join.EnrichLookupStatsBug SYNC} #129229 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index f796f903f7d2b..b7ec8ab1c80b6 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -522,6 +522,9 @@ tests: - class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT method: test {lookup-join.EnrichLookupStatsBug ASYNC} issue: https://github.com/elastic/elasticsearch/issues/129228 +- class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT + method: test {lookup-join.EnrichLookupStatsBug SYNC} + issue: https://github.com/elastic/elasticsearch/issues/129229 # Examples: # From d941e9b7e8dcc0d13f63f6c535a6a4201f39c89f Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Tue, 10 Jun 2025 12:47:11 -1000 Subject: [PATCH 049/102] [ES|QL] Specify population in StdDev docs (#129225) There are 2 types of Standard Deviation: population and sample, this commit clarifies that the existing is population. --- .../esql/_snippets/functions/description/std_dev.md | 2 +- .../esql/_snippets/functions/examples/std_dev.md | 2 +- .../esql/kibana/definition/functions/std_dev.json | 2 +- .../query-languages/esql/kibana/docs/functions/std_dev.md | 2 +- .../xpack/esql/expression/function/aggregate/StdDev.java | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/std_dev.md b/docs/reference/query-languages/esql/_snippets/functions/description/std_dev.md index 6f518bf874947..1e6ae637f428a 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/description/std_dev.md +++ b/docs/reference/query-languages/esql/_snippets/functions/description/std_dev.md @@ -2,5 +2,5 @@ **Description** -The standard deviation of a numeric field. +The population standard deviation of a numeric field. diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/std_dev.md b/docs/reference/query-languages/esql/_snippets/functions/examples/std_dev.md index d8aa421abbfc2..21d7cdfc26a71 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/std_dev.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/std_dev.md @@ -11,7 +11,7 @@ FROM employees | --- | | 0.20637044362020449 | -The expression can use inline functions. For example, to calculate the standard deviation of each employee’s maximum salary changes, first use `MV_MAX` on each row, and then use `STD_DEV` on the result +The expression can use inline functions. For example, to calculate the population standard deviation of each employee’s maximum salary changes, first use `MV_MAX` on each row, and then use `STD_DEV` on the result ```esql FROM employees diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/std_dev.json b/docs/reference/query-languages/esql/kibana/definition/functions/std_dev.json index e866688e3f451..f3afab6a9e721 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/std_dev.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/std_dev.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" : "agg", "name" : "std_dev", - "description" : "The standard deviation of a numeric field.", + "description" : "The population standard deviation of a numeric field.", "signatures" : [ { "params" : [ diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/std_dev.md b/docs/reference/query-languages/esql/kibana/docs/functions/std_dev.md index 1bc96bf39f6e1..d7b53fb2b24a3 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/std_dev.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/std_dev.md @@ -1,7 +1,7 @@ % This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. ### STD DEV -The standard deviation of a numeric field. +The population standard deviation of a numeric field. ```esql FROM employees diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/StdDev.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/StdDev.java index 10e604de89b3c..22a97c2ccb5b3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/StdDev.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/StdDev.java @@ -37,12 +37,12 @@ public class StdDev extends AggregateFunction implements ToAggregator { @FunctionInfo( returnType = "double", - description = "The standard deviation of a numeric field.", + description = "The population standard deviation of a numeric field.", type = FunctionType.AGGREGATE, examples = { @Example(file = "stats", tag = "stdev"), @Example( - description = "The expression can use inline functions. For example, to calculate the standard " + description = "The expression can use inline functions. For example, to calculate the population standard " + "deviation of each employee’s maximum salary changes, first use `MV_MAX` on each row, " + "and then use `STD_DEV` on the result", file = "stats", From cb4953363bd33402a0dbdf2be255ef3fb2893247 Mon Sep 17 00:00:00 2001 From: Sam Xiao Date: Wed, 11 Jun 2025 11:12:13 +0800 Subject: [PATCH 050/102] Unmute IngestGeoIpClientYamlTestSuiteIT (#129178) --- muted-tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/muted-tests.yml b/muted-tests.yml index b7ec8ab1c80b6..fcdf5106befc3 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -5,8 +5,6 @@ tests: - class: org.elasticsearch.smoketest.WatcherYamlRestIT method: test {p0=watcher/usage/10_basic/Test watcher usage stats output} issue: https://github.com/elastic/elasticsearch/issues/112189 -- class: org.elasticsearch.ingest.geoip.IngestGeoIpClientYamlTestSuiteIT - issue: https://github.com/elastic/elasticsearch/issues/111497 - class: org.elasticsearch.packaging.test.PackagesSecurityAutoConfigurationTests method: test20SecurityNotAutoConfiguredOnReInstallation issue: https://github.com/elastic/elasticsearch/issues/112635 From 941f2f1545bbfcb3fb6f6eb7c6bc35514857a35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?= Date: Wed, 11 Jun 2025 09:55:51 +0200 Subject: [PATCH 051/102] Fix an NPE in the ES|QL completion command. (#129235) --- .../bulk/BulkInferenceExecutionState.java | 2 +- .../inference/bulk/BulkInferenceExecutor.java | 22 ++++++++++--------- .../CompletionOperatorOutputBuilder.java | 5 +++++ .../CompletionOperatorRequestIterator.java | 4 ++++ .../inference/InferenceOperatorTestCase.java | 11 +++++++--- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutionState.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutionState.java index 307dae6c425c2..55f1f49f68c21 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutionState.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutionState.java @@ -75,7 +75,7 @@ public void markSeqNoAsPersisted(long seqNo) { * @param response The inference response. */ public synchronized void onInferenceResponse(long seqNo, InferenceAction.Response response) { - if (failureCollector.hasFailure() == false) { + if (response != null && failureCollector.hasFailure() == false) { bufferedResponses.put(seqNo, response); } checkpoint.markSeqNoAsProcessed(seqNo); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutor.java index d05a9a57d5265..257799962dda7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/bulk/BulkInferenceExecutor.java @@ -69,16 +69,19 @@ public void execute(BulkInferenceRequestIterator requests, ActionListener bulkExecutionState.onInferenceResponse(seqNo, r), - e -> bulkExecutionState.onInferenceException(seqNo, e) - ), - responseHandler::persistPendingResponses - ) + ActionListener inferenceResponseListener = ActionListener.runAfter( + ActionListener.wrap( + r -> bulkExecutionState.onInferenceResponse(seqNo, r), + e -> bulkExecutionState.onInferenceException(seqNo, e) + ), + responseHandler::persistPendingResponses ); + + if (request == null) { + inferenceResponseListener.onResponse(null); + } else { + throttledInferenceRunner.doInference(request, inferenceResponseListener); + } } } @@ -112,7 +115,6 @@ public synchronized void persistPendingResponses() { if (bulkExecutionState.hasFailure() == false) { try { InferenceAction.Response response = bulkExecutionState.fetchBufferedResponse(persistedSeqNo); - assert response != null; responses.add(response); } catch (Exception e) { bulkExecutionState.addFailure(e); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorOutputBuilder.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorOutputBuilder.java index d44a13786437a..cfb587c6451d8 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorOutputBuilder.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorOutputBuilder.java @@ -51,6 +51,11 @@ public void close() { */ @Override public void addInferenceResponse(InferenceAction.Response inferenceResponse) { + if (inferenceResponse == null) { + outputBlockBuilder.appendNull(); + return; + } + ChatCompletionResults completionResults = inferenceResults(inferenceResponse); if (completionResults == null) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorRequestIterator.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorRequestIterator.java index d7755a310098a..6893130425edf 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorRequestIterator.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/inference/completion/CompletionOperatorRequestIterator.java @@ -58,6 +58,10 @@ public InferenceAction.Request next() { * Wraps a single prompt string into an {@link InferenceAction.Request}. */ private InferenceAction.Request inferenceRequest(String prompt) { + if (prompt == null) { + return null; + } + return InferenceAction.Request.builder(inferenceId, TaskType.COMPLETION).setInput(List.of(prompt)).build(); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/inference/InferenceOperatorTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/inference/InferenceOperatorTestCase.java index 900e17f724156..c49e301968aa0 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/inference/InferenceOperatorTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/inference/InferenceOperatorTestCase.java @@ -88,12 +88,17 @@ protected int remaining() { @Override protected Page createPage(int positionOffset, int length) { length = Integer.min(length, remaining()); - try (var builder = blockFactory.newBytesRefVectorBuilder(length)) { + try (var builder = blockFactory.newBytesRefBlockBuilder(length)) { for (int i = 0; i < length; i++) { - builder.appendBytesRef(new BytesRef(randomAlphaOfLength(10))); + if (randomInt() % 100 == 0) { + builder.appendNull(); + } else { + builder.appendBytesRef(new BytesRef(randomAlphaOfLength(10))); + } + } currentPosition += length; - return new Page(builder.build().asBlock()); + return new Page(builder.build()); } } }; From 2e69b334ce70ef27bd7352fb090d8185d3a1e351 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 11 Jun 2025 10:09:50 +0200 Subject: [PATCH 052/102] ESQL: fix bwc test by adding min required version (#129204) Closes #129093 Closes #129094 Closes #129095 Closes #129102 Closes #129103 --- .../src/main/resources/where-like.csv-spec | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/where-like.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/where-like.csv-spec index 55ce333a60542..124fe82c32455 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/where-like.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/where-like.csv-spec @@ -320,7 +320,7 @@ emp_no:integer | job_positions:keyword 10025 | Accountant ; -likeWithUpperTurnedInsensitive +likeWithUpperTurnedInsensitive#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -332,7 +332,7 @@ emp_no:integer |first_name:keyword 10055 |Georgy ; -likeWithLowerTurnedInsensitive +likeWithLowerTurnedInsensitive#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -344,7 +344,7 @@ emp_no:integer |first_name:keyword 10055 |Georgy ; -likeWithLowerConflictingFolded +likeWithLowerConflictingFolded#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -354,7 +354,7 @@ FROM employees emp_no:integer |first_name:keyword ; -likeWithLowerTurnedInsensitiveNotPushedDown +likeWithLowerTurnedInsensitiveNotPushedDown#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -366,7 +366,7 @@ emp_no:integer |first_name:keyword 10055 |Georgy ; -rlikeWithUpperTurnedInsensitive +rlikeWithUpperTurnedInsensitive#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -378,7 +378,7 @@ emp_no:integer |first_name:keyword 10055 |Georgy ; -rlikeWithLowerTurnedInsensitive +rlikeWithLowerTurnedInsensitive#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -390,7 +390,7 @@ emp_no:integer |first_name:keyword 10055 |Georgy ; -rlikeWithLowerConflictingFolded +rlikeWithLowerConflictingFolded#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -400,7 +400,7 @@ FROM employees emp_no:integer |first_name:keyword ; -negatedRLikeWithLowerTurnedInsensitive +negatedRLikeWithLowerTurnedInsensitive#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no @@ -412,7 +412,7 @@ c:long 88 ; -rlikeWithLowerTurnedInsensitiveNotPushedDown +rlikeWithLowerTurnedInsensitiveNotPushedDown#[skip:-8.12.99] FROM employees | KEEP emp_no, first_name | SORT emp_no From 5f897ea5267817a6431acec42c758155641331ce Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 11 Jun 2025 10:10:36 +0200 Subject: [PATCH 053/102] ESQL: Fix test by add excluding capability (#129202) Closes #129078 Closes #129082 --- muted-tests.yml | 6 ------ .../qa/testFixtures/src/main/resources/lookup-join.csv-spec | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/muted-tests.yml b/muted-tests.yml index fcdf5106befc3..8812136ec2e17 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -496,12 +496,6 @@ tests: - class: org.elasticsearch.packaging.test.DockerTests method: test073RunEsAsDifferentUserAndGroupWithoutBindMounting issue: https://github.com/elastic/elasticsearch/issues/128996 -- class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT - method: test {lookup-join.LookupJoinOnTimeSeriesIndex ASYNC} - issue: https://github.com/elastic/elasticsearch/issues/129078 -- class: org.elasticsearch.xpack.esql.qa.mixed.MixedClusterEsqlSpecIT - method: test {lookup-join.LookupJoinOnTimeSeriesIndex SYNC} - issue: https://github.com/elastic/elasticsearch/issues/129082 - class: org.elasticsearch.upgrades.UpgradeClusterClientYamlTestSuiteIT method: test {p0=upgraded_cluster/70_ilm/Test Lifecycle Still There And Indices Are Still Managed} issue: https://github.com/elastic/elasticsearch/issues/129097 diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec index 4ccbf29fca5ac..c4bc19e274d64 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join.csv-spec @@ -4587,6 +4587,7 @@ emp_no:integer | language_code:integer | language_name:keyword lookupJoinOnTimeSeriesIndex required_capability: join_lookup_v12 +required_capability: lookup_join_on_mixed_numeric_fields FROM k8s | RENAME network.bytes_in AS language_code From 266baaa1a090b033298413b87f187f7affa0a0d3 Mon Sep 17 00:00:00 2001 From: Ievgen Degtiarenko Date: Wed, 11 Jun 2025 10:11:39 +0200 Subject: [PATCH 054/102] Fix vault field name (#129184) --- .buildkite/hooks/pre-command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/hooks/pre-command b/.buildkite/hooks/pre-command index 4c8a152e17c8b..34d614c7f7a5e 100644 --- a/.buildkite/hooks/pre-command +++ b/.buildkite/hooks/pre-command @@ -96,7 +96,7 @@ fi if [[ "${USE_PERF_CREDENTIALS:-}" == "true" ]]; then PERF_METRICS_HOST=$(vault read -field=es_host /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) - PERF_METRICS_USERNAME=$(vault read -field=es_username /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) + PERF_METRICS_USERNAME=$(vault read -field=es_user /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) PERF_METRICS_PASSWORD=$(vault read -field=es_password /secret/ci/elastic-elasticsearch/microbenchmarks-metrics) export PERF_METRICS_HOST From 44fac9365399ebceb3db61258cac653ef0335051 Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:21:00 +0200 Subject: [PATCH 055/102] Remove all usages of Metadata customs removal methods (#129043) This removes all non-test usage of ``` Metadata.Builder.removeProjectCustom(String) Metadata.Builder.removeProjectCustomIf(BiPredicate) ``` And replaces it with appropriate calls to the equivalent method on `ProjectMetadata.Builder` In most cases this _does not_ make the code project aware, but does reduce the number of deprecated methods in use. --- .../elasticsearch/cluster/ClusterStateDiffIT.java | 9 +++++++-- .../snapshots/CustomMetadataContextIT.java | 8 ++++---- .../cluster/coordination/RemoveCustomsCommand.java | 6 +++++- .../elasticsearch/cluster/metadata/Metadata.java | 12 ------------ .../cluster/ClusterChangedEventTests.java | 6 ++++-- .../cluster/metadata/MetadataTests.java | 14 -------------- .../PersistentTasksClusterServiceTests.java | 6 +++++- .../org/elasticsearch/xpack/CcrIntegTestCase.java | 8 ++++---- .../system_indices/task/SystemIndexMigrator.java | 6 +++--- 9 files changed, 32 insertions(+), 43 deletions(-) diff --git a/server/src/internalClusterTest/java/org/elasticsearch/cluster/ClusterStateDiffIT.java b/server/src/internalClusterTest/java/org/elasticsearch/cluster/ClusterStateDiffIT.java index 9c9056fcf6326..67cead7b17371 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/cluster/ClusterStateDiffIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/cluster/ClusterStateDiffIT.java @@ -26,6 +26,7 @@ import org.elasticsearch.cluster.metadata.IndexTemplateMetadata; import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; +import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; @@ -45,6 +46,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexVersion; @@ -706,12 +708,15 @@ public Metadata.Builder put(Metadata.Builder builder, Metadata.ProjectCustom par @Override public Metadata.Builder remove(Metadata.Builder builder, String name) { + @FixForMultiProject + final var projectBuilder = builder.getProject(ProjectId.DEFAULT); if (IndexGraveyard.TYPE.equals(name)) { // there must always be at least an empty graveyard - return builder.indexGraveyard(IndexGraveyard.builder().build()); + projectBuilder.indexGraveyard(IndexGraveyard.builder().build()); } else { - return builder.removeProjectCustom(name); + projectBuilder.removeCustom(name); } + return builder; } @Override diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/CustomMetadataContextIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/CustomMetadataContextIT.java index 08b4556dc4012..e4571789f9ec8 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/CustomMetadataContextIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/CustomMetadataContextIT.java @@ -91,13 +91,13 @@ public void testShouldRestoreOnlySnapshotMetadata() throws Exception { assertThat(getSnapshot("test-repo", "test-snapshot").state(), equalTo(SnapshotState.SUCCESS)); logger.info("update custom persistent metadata"); - updateClusterState(currentState -> currentState.copyAndUpdateMetadata(metadataBuilder -> { + updateClusterState(currentState -> currentState.copyAndUpdateProject(currentState.metadata().getProject().id(), builder -> { if (isSnapshotMetadataSet == false || randomBoolean()) { - metadataBuilder.putCustom(SnapshotMetadata.TYPE, new SnapshotMetadata("after_snapshot_s")); + builder.putCustom(SnapshotMetadata.TYPE, new SnapshotMetadata("after_snapshot_s")); } else { - metadataBuilder.removeProjectCustom(SnapshotMetadata.TYPE); + builder.removeCustom(SnapshotMetadata.TYPE); } - metadataBuilder.putCustom(ApiMetadata.TYPE, new ApiMetadata("after_snapshot_ns")); + builder.putCustom(ApiMetadata.TYPE, new ApiMetadata("after_snapshot_ns")); })); logger.info("restore snapshot"); diff --git a/server/src/main/java/org/elasticsearch/cluster/coordination/RemoveCustomsCommand.java b/server/src/main/java/org/elasticsearch/cluster/coordination/RemoveCustomsCommand.java index b1d284a1b5fad..d4ca5c82333e2 100644 --- a/server/src/main/java/org/elasticsearch/cluster/coordination/RemoveCustomsCommand.java +++ b/server/src/main/java/org/elasticsearch/cluster/coordination/RemoveCustomsCommand.java @@ -16,6 +16,8 @@ import org.elasticsearch.cli.UserException; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectId; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.core.Tuple; @@ -66,6 +68,8 @@ protected void processDataPaths(Terminal terminal, Path[] dataPaths, OptionSet o "project scoped custom metadata names: " + oldClusterState.metadata().getProject().customs().keySet() ); final Metadata.Builder metadataBuilder = Metadata.builder(oldClusterState.metadata()); + @FixForMultiProject + final ProjectMetadata.Builder projectBuilder = metadataBuilder.getProject(ProjectId.DEFAULT); for (String customToRemove : customsToRemove) { @FixForMultiProject boolean matched = false; @@ -82,7 +86,7 @@ protected void processDataPaths(Terminal terminal, Path[] dataPaths, OptionSet o } for (String customKey : oldClusterState.metadata().getProject().customs().keySet()) { if (Regex.simpleMatch(customToRemove, customKey)) { - metadataBuilder.removeProjectCustom(customKey); + projectBuilder.removeCustom(customKey); if (matched == false) { terminal.println("The following customs will be removed:"); } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java index 613e50228ef7a..dc55f4516d252 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java @@ -1743,18 +1743,6 @@ public Builder putProjectCustom(String type, ProjectCustom custom) { return this; } - @Deprecated(forRemoval = true) - public Builder removeProjectCustom(String type) { - getSingleProject().removeCustom(type); - return this; - } - - @Deprecated(forRemoval = true) - public Builder removeProjectCustomIf(BiPredicate p) { - getSingleProject().removeCustomIf(p); - return this; - } - @Deprecated(forRemoval = true) public Builder projectCustoms(Map projectCustoms) { projectCustoms.forEach((key, value) -> Objects.requireNonNull(value, key)); diff --git a/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java b/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java index 8c04ddd6163ff..ed5f6cf3ca411 100644 --- a/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java @@ -681,10 +681,12 @@ private static ClusterState nextState( final ClusterState.Builder builder = ClusterState.builder(previousState); builder.stateUUID(UUIDs.randomBase64UUID()); Metadata.Builder metadataBuilder = Metadata.builder(previousState.metadata()); + ProjectMetadata.Builder projectBuilder = ProjectMetadata.builder(previousState.metadata().projects().values().iterator().next()); metadataBuilder.removeCustomIf((ignore, custom) -> custom instanceof TestClusterCustomMetadata); - metadataBuilder.removeProjectCustomIf((ignore, custom) -> custom instanceof TestProjectCustomMetadata); + projectBuilder.removeCustomIf((ignore, custom) -> custom instanceof TestProjectCustomMetadata); clusterCustoms.forEach(clusterCustom -> metadataBuilder.putCustom(clusterCustom.getWriteableName(), clusterCustom)); - projectCustoms.forEach(projectCustom -> metadataBuilder.putCustom(projectCustom.getWriteableName(), projectCustom)); + projectCustoms.forEach(projectCustom -> projectBuilder.putCustom(projectCustom.getWriteableName(), projectCustom)); + metadataBuilder.put(projectBuilder); builder.metadata(metadataBuilder); return builder.build(); } diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java index 612c387af856c..b36b9de3dab8a 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java @@ -1391,20 +1391,6 @@ public void testBuilderRemoveClusterCustomIf() { assertThat(metadata.custom("custom2"), sameInstance(custom2)); } - public void testBuilderRemoveProjectCustomIf() { - var custom1 = new TestProjectCustomMetadata(); - var custom2 = new TestProjectCustomMetadata(); - var builder = Metadata.builder(); - builder.putCustom("custom1", custom1); - builder.putCustom("custom2", custom2); - - builder.removeProjectCustomIf((key, value) -> Objects.equals(key, "custom1")); - - var metadata = builder.build(); - assertThat(metadata.getProject().custom("custom1"), nullValue()); - assertThat(metadata.getProject().custom("custom2"), sameInstance(custom2)); - } - public void testBuilderRejectsDataStreamThatConflictsWithIndex() { final String dataStreamName = "my-data-stream"; IndexMetadata idx = createFirstBackingIndex(dataStreamName).build(); diff --git a/server/src/test/java/org/elasticsearch/persistent/PersistentTasksClusterServiceTests.java b/server/src/test/java/org/elasticsearch/persistent/PersistentTasksClusterServiceTests.java index f0f87e7ff641b..b79f2f6517189 100644 --- a/server/src/test/java/org/elasticsearch/persistent/PersistentTasksClusterServiceTests.java +++ b/server/src/test/java/org/elasticsearch/persistent/PersistentTasksClusterServiceTests.java @@ -20,6 +20,7 @@ import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeRole; @@ -933,7 +934,10 @@ private ClusterState insignificantChange(ClusterState clusterState) { if (scope == PersistentTasksExecutor.Scope.CLUSTER) { metadata = Metadata.builder(clusterState.metadata()).removeCustom(ClusterPersistentTasksCustomMetadata.TYPE); } else { - metadata = Metadata.builder(clusterState.metadata()).removeProjectCustom(PersistentTasksCustomMetadata.TYPE); + metadata = Metadata.builder(clusterState.metadata()) + .put( + ProjectMetadata.builder(clusterState.metadata().getProject()).removeCustom(PersistentTasksCustomMetadata.TYPE) + ); } return builder.metadata(metadata).build(); } diff --git a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java index 697efcca0f159..644a2a074ac59 100644 --- a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java +++ b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java @@ -31,7 +31,7 @@ import org.elasticsearch.cluster.RestoreInProgress; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.UnassignedInfo; import org.elasticsearch.cluster.routing.allocation.DiskThresholdSettings; @@ -865,10 +865,10 @@ static void removeCCRRelatedMetadataFromClusterState(ClusterService clusterServi public ClusterState execute(ClusterState currentState) throws Exception { AutoFollowMetadata empty = new AutoFollowMetadata(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); ClusterState.Builder newState = ClusterState.builder(currentState); - newState.metadata( - Metadata.builder(currentState.getMetadata()) + newState.putProjectMetadata( + ProjectMetadata.builder(currentState.metadata().getProject()) .putCustom(AutoFollowMetadata.TYPE, empty) - .removeProjectCustom(PersistentTasksCustomMetadata.TYPE) + .removeCustom(PersistentTasksCustomMetadata.TYPE) .build() ); return newState.build(); diff --git a/x-pack/plugin/migrate/src/main/java/org/elasticsearch/system_indices/task/SystemIndexMigrator.java b/x-pack/plugin/migrate/src/main/java/org/elasticsearch/system_indices/task/SystemIndexMigrator.java index 57e7af8411944..82d9d190ffc20 100644 --- a/x-pack/plugin/migrate/src/main/java/org/elasticsearch/system_indices/task/SystemIndexMigrator.java +++ b/x-pack/plugin/migrate/src/main/java/org/elasticsearch/system_indices/task/SystemIndexMigrator.java @@ -29,7 +29,6 @@ import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.IndexTemplateMetadata; -import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService; import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.service.ClusterService; @@ -863,9 +862,10 @@ private static void clearResults(ClusterService clusterService, ActionListener Date: Wed, 11 Jun 2025 10:24:33 +0200 Subject: [PATCH 056/102] Replace tuple with record (#128976) --- .../core/expression/MetadataAttribute.java | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java index 3260489983abd..1802791514803 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/MetadataAttribute.java @@ -11,7 +11,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.core.Nullable; -import org.elasticsearch.core.Tuple; import org.elasticsearch.index.mapper.IdFieldMapper; import org.elasticsearch.index.mapper.IgnoredFieldMapper; import org.elasticsearch.index.mapper.IndexModeFieldMapper; @@ -26,8 +25,6 @@ import java.util.Map; import java.util.Objects; -import static org.elasticsearch.core.Tuple.tuple; - public class MetadataAttribute extends TypedAttribute { public static final String TIMESTAMP_FIELD = "@timestamp"; // this is not a true metadata attribute public static final String TSID_FIELD = "_tsid"; @@ -40,23 +37,19 @@ public class MetadataAttribute extends TypedAttribute { MetadataAttribute::readFrom ); - private static final Map> ATTRIBUTES_MAP = Map.of( - "_version", - tuple(DataType.LONG, false), // _version field is not searchable - INDEX, - tuple(DataType.KEYWORD, true), - IdFieldMapper.NAME, - tuple(DataType.KEYWORD, false), // actually searchable, but fielddata access on the _id field is disallowed by default - IgnoredFieldMapper.NAME, - tuple(DataType.KEYWORD, true), - SourceFieldMapper.NAME, - tuple(DataType.SOURCE, false), - IndexModeFieldMapper.NAME, - tuple(DataType.KEYWORD, true), - SCORE, - tuple(DataType.DOUBLE, false) + private static final Map ATTRIBUTES_MAP = Map.ofEntries( + Map.entry("_version", new MetadataAttributeConfiguration(DataType.LONG, false)), + Map.entry(INDEX, new MetadataAttributeConfiguration(DataType.KEYWORD, true)), + // actually _id is searchable, but fielddata access on it is disallowed by default + Map.entry(IdFieldMapper.NAME, new MetadataAttributeConfiguration(DataType.KEYWORD, false)), + Map.entry(IgnoredFieldMapper.NAME, new MetadataAttributeConfiguration(DataType.KEYWORD, true)), + Map.entry(SourceFieldMapper.NAME, new MetadataAttributeConfiguration(DataType.SOURCE, false)), + Map.entry(IndexModeFieldMapper.NAME, new MetadataAttributeConfiguration(DataType.KEYWORD, true)), + Map.entry(SCORE, new MetadataAttributeConfiguration(DataType.DOUBLE, false)) ); + private record MetadataAttributeConfiguration(DataType dataType, boolean searchable) {} + private final boolean searchable; public MetadataAttribute( @@ -160,12 +153,12 @@ public boolean searchable() { public static MetadataAttribute create(Source source, String name) { var t = ATTRIBUTES_MAP.get(name); - return t != null ? new MetadataAttribute(source, name, t.v1(), t.v2()) : null; + return t != null ? new MetadataAttribute(source, name, t.dataType(), t.searchable()) : null; } public static DataType dataType(String name) { var t = ATTRIBUTES_MAP.get(name); - return t != null ? t.v1() : null; + return t != null ? t.dataType() : null; } public static boolean isSupported(String name) { From 2f85047f3362411c7ef329563d4a167a9df0f489 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Wed, 11 Jun 2025 10:15:21 +0100 Subject: [PATCH 057/102] improve support for bytecode patching signed jars (#128613) * improve support for bytecode patching signed jars * Update docs/changelog/128613.yaml --------- Co-authored-by: elasticsearchmachine Co-authored-by: Johannes Freden Jansson --- .../internal/dependencies/patches/Utils.java | 29 +++++++++++++++++-- docs/changelog/128613.yaml | 5 ++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 docs/changelog/128613.yaml diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java index 63831b6f062ce..8903e1a6aa0b8 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java @@ -24,9 +24,11 @@ import java.util.HexFormat; import java.util.Locale; import java.util.function.Function; +import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import java.util.stream.Collectors; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; @@ -60,6 +62,10 @@ public String toString() { } } + public static void patchJar(File inputJar, File outputJar, Collection patchers) { + patchJar(inputJar, outputJar, patchers, false); + } + /** * Patches the classes in the input JAR file, using the collection of patchers. Each patcher specifies a target class (its jar entry * name) and the SHA256 digest on the class bytes. @@ -69,8 +75,11 @@ public String toString() { * @param inputFile the JAR file to patch * @param outputFile the output (patched) JAR file * @param patchers list of patcher info (classes to patch (jar entry name + optional SHA256 digest) and ASM visitor to transform them) + * @param unsignJar whether to remove class signatures from the JAR Manifest; set this to true when patching a signed JAR, + * otherwise the patched classes will fail to load at runtime due to mismatched signatures. + * @see Understanding Signing and Verification */ - public static void patchJar(File inputFile, File outputFile, Collection patchers) { + public static void patchJar(File inputFile, File outputFile, Collection patchers, boolean unsignJar) { var classPatchers = patchers.stream().collect(Collectors.toMap(PatcherInfo::jarEntryName, Function.identity())); var mismatchedClasses = new ArrayList(); try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) { @@ -101,9 +110,23 @@ public static void patchJar(File inputFile, File outputFile, Collection Date: Wed, 11 Jun 2025 11:29:56 +0200 Subject: [PATCH 058/102] rename ES|QL sample capability (#129193) --- .../esql/qa/rest/RestSampleTestCase.java | 2 +- .../src/main/resources/sample.csv-spec | 26 +++++++++---------- .../xpack/esql/action/EsqlCapabilities.java | 2 +- .../xpack/esql/analysis/AnalyzerTests.java | 2 +- .../optimizer/LogicalPlanOptimizerTests.java | 14 +++++----- .../optimizer/PhysicalPlanOptimizerTests.java | 2 +- .../esql/parser/StatementParserTests.java | 2 +- 7 files changed, 25 insertions(+), 25 deletions(-) diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java index 2362c163d57d8..1b928df3367fc 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestSampleTestCase.java @@ -33,7 +33,7 @@ public class RestSampleTestCase extends ESRestTestCase { public void skipWhenSampleDisabled() throws IOException { assumeTrue( "Requires SAMPLE capability", - EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.SAMPLE.capabilityName())) + EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.SAMPLE_V2.capabilityName())) ); } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/sample.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/sample.csv-spec index 237eee40e60b7..da14b7d3ebc36 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/sample.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/sample.csv-spec @@ -9,7 +9,7 @@ // because the CSV tests don't support such assertions. row -required_capability: sample +required_capability: sample_v2 ROW x = 1 | SAMPLE .999999999 ; @@ -20,7 +20,7 @@ x:integer row and mv_expand -required_capability: sample +required_capability: sample_v2 ROW x = [1,2,3,4,5] | MV_EXPAND x | SAMPLE .999999999 ; @@ -35,7 +35,7 @@ x:integer adjust stats for sampling -required_capability: sample +required_capability: sample_v2 FROM employees | SAMPLE 0.5 @@ -53,7 +53,7 @@ true before where -required_capability: sample +required_capability: sample_v2 FROM employees | SAMPLE 0.5 @@ -71,7 +71,7 @@ true after where -required_capability: sample +required_capability: sample_v2 FROM employees | WHERE emp_no <= 10050 @@ -89,7 +89,7 @@ true before sort -required_capability: sample +required_capability: sample_v2 FROM employees | SAMPLE 0.5 @@ -107,7 +107,7 @@ true after sort -required_capability: sample +required_capability: sample_v2 FROM employees | SORT emp_no @@ -125,7 +125,7 @@ true before limit -required_capability: sample +required_capability: sample_v2 FROM employees | SAMPLE 0.5 @@ -141,7 +141,7 @@ true after limit -required_capability: sample +required_capability: sample_v2 FROM employees | LIMIT 50 @@ -158,7 +158,7 @@ true before mv_expand -required_capability: sample +required_capability: sample_v2 ROW x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50], y = [1,2] | MV_EXPAND x @@ -176,7 +176,7 @@ true after mv_expand -required_capability: sample +required_capability: sample_v2 ROW x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50], y = [1,2] | MV_EXPAND x @@ -194,7 +194,7 @@ true multiple samples -required_capability: sample +required_capability: sample_v2 FROM employees | SAMPLE 0.7 @@ -213,7 +213,7 @@ true after stats -required_capability: sample +required_capability: sample_v2 FROM employees | SAMPLE 0.5 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 d25f5b9e81d5b..1359cac294876 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 @@ -1077,7 +1077,7 @@ public enum Cap { /** * Support for the SAMPLE command */ - SAMPLE(Build.current().isSnapshot()), + SAMPLE_V2(Build.current().isSnapshot()), /** * The {@code _query} API now gives a cast recommendation if multiple types are found in certain instances. diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index df4cc64759676..402c09afc3c8a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -3460,7 +3460,7 @@ public void testRrfError() { } public void testRandomSampleProbability() { - assumeTrue("requires SAMPLE capability", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("requires SAMPLE capability", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var e = expectThrows(VerificationException.class, () -> analyze("FROM test | SAMPLE 1.")); assertThat(e.getMessage(), containsString("RandomSampling probability must be strictly between 0.0 and 1.0, was [1.0]")); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index c01da28c59e2b..926fdd75cac6a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -7858,7 +7858,7 @@ public void testPruneRedundantOrderBy() { * \_EsRelation[test][_meta_field{f}#12, emp_no{f}#6, first_name{f}#7, ge..] */ public void testSampleMerged() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var query = """ FROM TEST @@ -7879,7 +7879,7 @@ public void testSampleMerged() { } public void testSamplePushDown() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); for (var command : List.of( "ENRICH languages_idx on first_name", @@ -7904,7 +7904,7 @@ public void testSamplePushDown() { } public void testSamplePushDown_sort() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var query = "FROM TEST | WHERE emp_no > 0 | SAMPLE 0.5 | LIMIT 100"; var optimized = optimizedPlan(query); @@ -7918,7 +7918,7 @@ public void testSamplePushDown_sort() { } public void testSamplePushDown_where() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var query = "FROM TEST | SORT emp_no | SAMPLE 0.5 | LIMIT 100"; var optimized = optimizedPlan(query); @@ -7931,7 +7931,7 @@ public void testSamplePushDown_where() { } public void testSampleNoPushDown() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); for (var command : List.of("LIMIT 100", "MV_EXPAND languages", "STATS COUNT()")) { var query = "FROM TEST | " + command + " | SAMPLE .5"; @@ -7953,7 +7953,7 @@ public void testSampleNoPushDown() { * \_EsRelation[languages_lookup][LOOKUP][language_code{f}#17, language_name{f}#18] */ public void testSampleNoPushDownLookupJoin() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var query = """ FROM TEST @@ -7979,7 +7979,7 @@ public void testSampleNoPushDownLookupJoin() { * \_EsRelation[test][_meta_field{f}#12, emp_no{f}#6, first_name{f}#7, ge..] */ public void testSampleNoPushDownChangePoint() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var query = """ FROM TEST diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index 2d47498b7e0d3..cbca9b3eaae88 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -8045,7 +8045,7 @@ public void testNotEqualsPushdownToDelegate() { * [_doc{f}#24], limit[1000], sort[] estimatedRowSize[332] */ public void testSamplePushDown() { - assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("sample must be enabled", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); var plan = physicalPlan(""" FROM test 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 11733b7c4723d..51add342453bd 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 @@ -3482,7 +3482,7 @@ public void testInvalidCompletion() { } public void testSample() { - assumeTrue("SAMPLE requires corresponding capability", EsqlCapabilities.Cap.SAMPLE.isEnabled()); + assumeTrue("SAMPLE requires corresponding capability", EsqlCapabilities.Cap.SAMPLE_V2.isEnabled()); expectError("FROM test | SAMPLE .1 2", "line 1:23: extraneous input '2' expecting "); expectError("FROM test | SAMPLE .1 \"2\"", "line 1:23: extraneous input '\"2\"' expecting "); expectError("FROM test | SAMPLE 1", "line 1:20: mismatched input '1' expecting {DECIMAL_LITERAL, '+', '-'}"); From 044d81003f77131b808fa5535f41307ccfe8ad9f Mon Sep 17 00:00:00 2001 From: Alexander Spies Date: Wed, 11 Jun 2025 11:38:13 +0200 Subject: [PATCH 059/102] ESQL: Mute GenerativeForkIT for some LOOKUP JOIN tests (#129248) --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 8812136ec2e17..ed3971f523f39 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -517,6 +517,9 @@ tests: - class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT method: test {lookup-join.EnrichLookupStatsBug SYNC} issue: https://github.com/elastic/elasticsearch/issues/129229 +- class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT + method: test {lookup-join.MultipleBatches* + issue: https://github.com/elastic/elasticsearch/issues/129210 # Examples: # From 4b3adc57e44d684a597778a2807b673918b4e586 Mon Sep 17 00:00:00 2001 From: Bogdan Pintea Date: Wed, 11 Jun 2025 13:16:48 +0200 Subject: [PATCH 060/102] ESQL: Extend `RENAME` syntax to allow a `new = old` syntax (#129212) This extends RENAME's grammar to allow a new syntax: `| RENAME new_name = old_name` This is supported along the existing `... old_name AS new_name` syntax. Closes #129208 --- .../esql/_snippets/commands/layout/rename.md | 10 + .../src/main/resources/rename.csv-spec | 47 + .../esql/src/main/antlr/EsqlBaseParser.g4 | 1 + .../xpack/esql/action/EsqlCapabilities.java | 5 + .../xpack/esql/parser/EsqlBaseParser.interp | 2 +- .../xpack/esql/parser/EsqlBaseParser.java | 1458 +++++++++-------- .../xpack/esql/parser/ExpressionTests.java | 3 +- 7 files changed, 806 insertions(+), 720 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/commands/layout/rename.md b/docs/reference/query-languages/esql/_snippets/commands/layout/rename.md index a6aaf3ca93b27..5bcade39660e7 100644 --- a/docs/reference/query-languages/esql/_snippets/commands/layout/rename.md +++ b/docs/reference/query-languages/esql/_snippets/commands/layout/rename.md @@ -8,6 +8,16 @@ The `RENAME` processing command renames one or more columns. RENAME old_name1 AS new_name1[, ..., old_nameN AS new_nameN] ``` +The following syntax is also supported {applies_to}`stack: ga 9.1`: + +```esql +RENAME new_name1 = old_name1[, ..., new_nameN = old_nameN] +``` + +::::{tip} +Both syntax options can be used interchangeably but we recommend sticking to one for consistency and readability. +:::: + **Parameters** `old_nameX` diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/rename.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/rename.csv-spec index 71603585dc64e..f485e39233982 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/rename.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/rename.csv-spec @@ -296,3 +296,50 @@ ROW a="keyword", b=5, c=null a:integer 5 ; + +useAssignmentOnly +required_capability: rename_allow_assignment + +ROW a = 1, b = "two" +| RENAME aa = a, bb = b +; + +aa:integer | bb:keyword +1 | two +; + +useAssignmentAndASKeyword +required_capability: rename_allow_assignment + +ROW a = 1, b = "two", c = null +| RENAME aa = a, b AS bb, cc = c +; + +aa:integer | bb:keyword | cc:null +1 | two | null +; + +shadowWithAssignment +required_capability: rename_allow_assignment + +ROW a = 1, b = "two" +| RENAME a = b +; + +a:keyword +two +; + +multipleRenamesWithAssignment +required_capability: rename_sequential_processing +required_capability: rename_allow_assignment + +ROW a="keyword", b=5, c=null +| RENAME c = a, a = b +| RENAME b = c +| RENAME b = a, a = b +; + +a:integer +5 +; diff --git a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 index bb346cf60550a..47f1ce3fbc204 100644 --- a/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 +++ b/x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4 @@ -212,6 +212,7 @@ renameCommand renameClause: oldName=qualifiedNamePattern AS newName=qualifiedNamePattern + | newName=qualifiedNamePattern ASSIGN oldName=qualifiedNamePattern ; dissectCommand 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 1359cac294876..80b1644c84d53 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 @@ -439,6 +439,11 @@ public enum Cap { */ RENAME_SEQUENTIAL_PROCESSING, + /** + * Support for assignment in RENAME, besides the use of `AS` keyword. + */ + RENAME_ALLOW_ASSIGNMENT, + /** * Support for removing empty attribute in merging output. * See ESQL: EVAL after STATS produces an empty column #126392 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 7638709eb9577..5a6c84e68cfb4 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 @@ -368,4 +368,4 @@ joinPredicate atn: -[4, 1, 139, 772, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 410, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 422, 8, 41, 10, 41, 12, 41, 425, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 445, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 451, 8, 46, 10, 46, 12, 46, 454, 9, 46, 3, 46, 456, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 461, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 474, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 480, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 487, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 496, 8, 53, 11, 53, 12, 53, 497, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 510, 8, 55, 10, 55, 12, 55, 513, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 521, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 531, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 537, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 553, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 560, 8, 61, 10, 61, 12, 61, 563, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 570, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 575, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 583, 8, 61, 10, 61, 12, 61, 586, 9, 61, 1, 62, 1, 62, 3, 62, 590, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 597, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 602, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 607, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 617, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 623, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 631, 8, 65, 10, 65, 12, 65, 634, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 644, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 649, 8, 66, 10, 66, 12, 66, 652, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 660, 8, 67, 10, 67, 12, 67, 663, 9, 67, 1, 67, 1, 67, 3, 67, 667, 8, 67, 3, 67, 669, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 679, 8, 69, 10, 69, 12, 69, 682, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 703, 8, 71, 10, 71, 12, 71, 706, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 714, 8, 71, 10, 71, 12, 71, 717, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 725, 8, 71, 10, 71, 12, 71, 728, 9, 71, 1, 71, 1, 71, 3, 71, 732, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 738, 8, 73, 1, 74, 3, 74, 741, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 746, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 765, 8, 80, 10, 80, 12, 80, 768, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 803, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 401, 1, 0, 0, 0, 76, 405, 1, 0, 0, 0, 78, 411, 1, 0, 0, 0, 80, 415, 1, 0, 0, 0, 82, 418, 1, 0, 0, 0, 84, 426, 1, 0, 0, 0, 86, 430, 1, 0, 0, 0, 88, 433, 1, 0, 0, 0, 90, 437, 1, 0, 0, 0, 92, 440, 1, 0, 0, 0, 94, 460, 1, 0, 0, 0, 96, 464, 1, 0, 0, 0, 98, 469, 1, 0, 0, 0, 100, 475, 1, 0, 0, 0, 102, 488, 1, 0, 0, 0, 104, 491, 1, 0, 0, 0, 106, 495, 1, 0, 0, 0, 108, 499, 1, 0, 0, 0, 110, 503, 1, 0, 0, 0, 112, 520, 1, 0, 0, 0, 114, 522, 1, 0, 0, 0, 116, 524, 1, 0, 0, 0, 118, 532, 1, 0, 0, 0, 120, 542, 1, 0, 0, 0, 122, 574, 1, 0, 0, 0, 124, 601, 1, 0, 0, 0, 126, 603, 1, 0, 0, 0, 128, 616, 1, 0, 0, 0, 130, 622, 1, 0, 0, 0, 132, 643, 1, 0, 0, 0, 134, 653, 1, 0, 0, 0, 136, 672, 1, 0, 0, 0, 138, 674, 1, 0, 0, 0, 140, 685, 1, 0, 0, 0, 142, 731, 1, 0, 0, 0, 144, 733, 1, 0, 0, 0, 146, 737, 1, 0, 0, 0, 148, 740, 1, 0, 0, 0, 150, 745, 1, 0, 0, 0, 152, 749, 1, 0, 0, 0, 154, 751, 1, 0, 0, 0, 156, 753, 1, 0, 0, 0, 158, 758, 1, 0, 0, 0, 160, 760, 1, 0, 0, 0, 162, 769, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 75, 1, 0, 0, 0, 405, 406, 5, 8, 0, 0, 406, 407, 3, 132, 66, 0, 407, 409, 3, 152, 76, 0, 408, 410, 3, 82, 41, 0, 409, 408, 1, 0, 0, 0, 409, 410, 1, 0, 0, 0, 410, 77, 1, 0, 0, 0, 411, 412, 5, 10, 0, 0, 412, 413, 3, 132, 66, 0, 413, 414, 3, 152, 76, 0, 414, 79, 1, 0, 0, 0, 415, 416, 5, 27, 0, 0, 416, 417, 3, 46, 23, 0, 417, 81, 1, 0, 0, 0, 418, 423, 3, 84, 42, 0, 419, 420, 5, 62, 0, 0, 420, 422, 3, 84, 42, 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, 83, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 427, 3, 52, 26, 0, 427, 428, 5, 58, 0, 0, 428, 429, 3, 142, 71, 0, 429, 85, 1, 0, 0, 0, 430, 431, 5, 6, 0, 0, 431, 432, 3, 88, 44, 0, 432, 87, 1, 0, 0, 0, 433, 434, 5, 97, 0, 0, 434, 435, 3, 2, 1, 0, 435, 436, 5, 98, 0, 0, 436, 89, 1, 0, 0, 0, 437, 438, 5, 33, 0, 0, 438, 439, 5, 136, 0, 0, 439, 91, 1, 0, 0, 0, 440, 441, 5, 5, 0, 0, 441, 444, 5, 38, 0, 0, 442, 443, 5, 74, 0, 0, 443, 445, 3, 48, 24, 0, 444, 442, 1, 0, 0, 0, 444, 445, 1, 0, 0, 0, 445, 455, 1, 0, 0, 0, 446, 447, 5, 79, 0, 0, 447, 452, 3, 94, 47, 0, 448, 449, 5, 62, 0, 0, 449, 451, 3, 94, 47, 0, 450, 448, 1, 0, 0, 0, 451, 454, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 446, 1, 0, 0, 0, 455, 456, 1, 0, 0, 0, 456, 93, 1, 0, 0, 0, 457, 458, 3, 48, 24, 0, 458, 459, 5, 58, 0, 0, 459, 461, 1, 0, 0, 0, 460, 457, 1, 0, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 463, 3, 48, 24, 0, 463, 95, 1, 0, 0, 0, 464, 465, 5, 26, 0, 0, 465, 466, 3, 28, 14, 0, 466, 467, 5, 74, 0, 0, 467, 468, 3, 50, 25, 0, 468, 97, 1, 0, 0, 0, 469, 470, 5, 16, 0, 0, 470, 473, 3, 42, 21, 0, 471, 472, 5, 59, 0, 0, 472, 474, 3, 14, 7, 0, 473, 471, 1, 0, 0, 0, 473, 474, 1, 0, 0, 0, 474, 99, 1, 0, 0, 0, 475, 476, 5, 4, 0, 0, 476, 479, 3, 46, 23, 0, 477, 478, 5, 74, 0, 0, 478, 480, 3, 46, 23, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 486, 1, 0, 0, 0, 481, 482, 5, 132, 0, 0, 482, 483, 3, 46, 23, 0, 483, 484, 5, 62, 0, 0, 484, 485, 3, 46, 23, 0, 485, 487, 1, 0, 0, 0, 486, 481, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 101, 1, 0, 0, 0, 488, 489, 5, 30, 0, 0, 489, 490, 3, 50, 25, 0, 490, 103, 1, 0, 0, 0, 491, 492, 5, 21, 0, 0, 492, 493, 3, 106, 53, 0, 493, 105, 1, 0, 0, 0, 494, 496, 3, 108, 54, 0, 495, 494, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 107, 1, 0, 0, 0, 499, 500, 5, 99, 0, 0, 500, 501, 3, 110, 55, 0, 501, 502, 5, 100, 0, 0, 502, 109, 1, 0, 0, 0, 503, 504, 6, 55, -1, 0, 504, 505, 3, 112, 56, 0, 505, 511, 1, 0, 0, 0, 506, 507, 10, 1, 0, 0, 507, 508, 5, 52, 0, 0, 508, 510, 3, 112, 56, 0, 509, 506, 1, 0, 0, 0, 510, 513, 1, 0, 0, 0, 511, 509, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 111, 1, 0, 0, 0, 513, 511, 1, 0, 0, 0, 514, 521, 3, 38, 19, 0, 515, 521, 3, 8, 4, 0, 516, 521, 3, 62, 31, 0, 517, 521, 3, 40, 20, 0, 518, 521, 3, 64, 32, 0, 519, 521, 3, 76, 38, 0, 520, 514, 1, 0, 0, 0, 520, 515, 1, 0, 0, 0, 520, 516, 1, 0, 0, 0, 520, 517, 1, 0, 0, 0, 520, 518, 1, 0, 0, 0, 520, 519, 1, 0, 0, 0, 521, 113, 1, 0, 0, 0, 522, 523, 5, 31, 0, 0, 523, 115, 1, 0, 0, 0, 524, 525, 5, 17, 0, 0, 525, 526, 3, 142, 71, 0, 526, 527, 5, 74, 0, 0, 527, 530, 3, 18, 9, 0, 528, 529, 5, 79, 0, 0, 529, 531, 3, 60, 30, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 117, 1, 0, 0, 0, 532, 536, 5, 7, 0, 0, 533, 534, 3, 46, 23, 0, 534, 535, 5, 58, 0, 0, 535, 537, 1, 0, 0, 0, 536, 533, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 3, 132, 66, 0, 539, 540, 5, 79, 0, 0, 540, 541, 3, 60, 30, 0, 541, 119, 1, 0, 0, 0, 542, 543, 5, 18, 0, 0, 543, 544, 3, 148, 74, 0, 544, 121, 1, 0, 0, 0, 545, 546, 6, 61, -1, 0, 546, 547, 5, 71, 0, 0, 547, 575, 3, 122, 61, 8, 548, 575, 3, 128, 64, 0, 549, 575, 3, 124, 62, 0, 550, 552, 3, 128, 64, 0, 551, 553, 5, 71, 0, 0, 552, 551, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 555, 5, 67, 0, 0, 555, 556, 5, 99, 0, 0, 556, 561, 3, 128, 64, 0, 557, 558, 5, 62, 0, 0, 558, 560, 3, 128, 64, 0, 559, 557, 1, 0, 0, 0, 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 564, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 5, 100, 0, 0, 565, 575, 1, 0, 0, 0, 566, 567, 3, 128, 64, 0, 567, 569, 5, 68, 0, 0, 568, 570, 5, 71, 0, 0, 569, 568, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 1, 0, 0, 0, 571, 572, 5, 72, 0, 0, 572, 575, 1, 0, 0, 0, 573, 575, 3, 126, 63, 0, 574, 545, 1, 0, 0, 0, 574, 548, 1, 0, 0, 0, 574, 549, 1, 0, 0, 0, 574, 550, 1, 0, 0, 0, 574, 566, 1, 0, 0, 0, 574, 573, 1, 0, 0, 0, 575, 584, 1, 0, 0, 0, 576, 577, 10, 5, 0, 0, 577, 578, 5, 56, 0, 0, 578, 583, 3, 122, 61, 6, 579, 580, 10, 4, 0, 0, 580, 581, 5, 75, 0, 0, 581, 583, 3, 122, 61, 5, 582, 576, 1, 0, 0, 0, 582, 579, 1, 0, 0, 0, 583, 586, 1, 0, 0, 0, 584, 582, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 123, 1, 0, 0, 0, 586, 584, 1, 0, 0, 0, 587, 589, 3, 128, 64, 0, 588, 590, 5, 71, 0, 0, 589, 588, 1, 0, 0, 0, 589, 590, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 592, 5, 70, 0, 0, 592, 593, 3, 152, 76, 0, 593, 602, 1, 0, 0, 0, 594, 596, 3, 128, 64, 0, 595, 597, 5, 71, 0, 0, 596, 595, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 1, 0, 0, 0, 598, 599, 5, 77, 0, 0, 599, 600, 3, 152, 76, 0, 600, 602, 1, 0, 0, 0, 601, 587, 1, 0, 0, 0, 601, 594, 1, 0, 0, 0, 602, 125, 1, 0, 0, 0, 603, 606, 3, 46, 23, 0, 604, 605, 5, 60, 0, 0, 605, 607, 3, 10, 5, 0, 606, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 5, 61, 0, 0, 609, 610, 3, 142, 71, 0, 610, 127, 1, 0, 0, 0, 611, 617, 3, 130, 65, 0, 612, 613, 3, 130, 65, 0, 613, 614, 3, 154, 77, 0, 614, 615, 3, 130, 65, 0, 615, 617, 1, 0, 0, 0, 616, 611, 1, 0, 0, 0, 616, 612, 1, 0, 0, 0, 617, 129, 1, 0, 0, 0, 618, 619, 6, 65, -1, 0, 619, 623, 3, 132, 66, 0, 620, 621, 7, 4, 0, 0, 621, 623, 3, 130, 65, 3, 622, 618, 1, 0, 0, 0, 622, 620, 1, 0, 0, 0, 623, 632, 1, 0, 0, 0, 624, 625, 10, 2, 0, 0, 625, 626, 7, 5, 0, 0, 626, 631, 3, 130, 65, 3, 627, 628, 10, 1, 0, 0, 628, 629, 7, 4, 0, 0, 629, 631, 3, 130, 65, 2, 630, 624, 1, 0, 0, 0, 630, 627, 1, 0, 0, 0, 631, 634, 1, 0, 0, 0, 632, 630, 1, 0, 0, 0, 632, 633, 1, 0, 0, 0, 633, 131, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 635, 636, 6, 66, -1, 0, 636, 644, 3, 142, 71, 0, 637, 644, 3, 46, 23, 0, 638, 644, 3, 134, 67, 0, 639, 640, 5, 99, 0, 0, 640, 641, 3, 122, 61, 0, 641, 642, 5, 100, 0, 0, 642, 644, 1, 0, 0, 0, 643, 635, 1, 0, 0, 0, 643, 637, 1, 0, 0, 0, 643, 638, 1, 0, 0, 0, 643, 639, 1, 0, 0, 0, 644, 650, 1, 0, 0, 0, 645, 646, 10, 1, 0, 0, 646, 647, 5, 60, 0, 0, 647, 649, 3, 10, 5, 0, 648, 645, 1, 0, 0, 0, 649, 652, 1, 0, 0, 0, 650, 648, 1, 0, 0, 0, 650, 651, 1, 0, 0, 0, 651, 133, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 654, 3, 136, 68, 0, 654, 668, 5, 99, 0, 0, 655, 669, 5, 89, 0, 0, 656, 661, 3, 122, 61, 0, 657, 658, 5, 62, 0, 0, 658, 660, 3, 122, 61, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 666, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 62, 0, 0, 665, 667, 3, 138, 69, 0, 666, 664, 1, 0, 0, 0, 666, 667, 1, 0, 0, 0, 667, 669, 1, 0, 0, 0, 668, 655, 1, 0, 0, 0, 668, 656, 1, 0, 0, 0, 668, 669, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 5, 100, 0, 0, 671, 135, 1, 0, 0, 0, 672, 673, 3, 60, 30, 0, 673, 137, 1, 0, 0, 0, 674, 675, 5, 92, 0, 0, 675, 680, 3, 140, 70, 0, 676, 677, 5, 62, 0, 0, 677, 679, 3, 140, 70, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 683, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 5, 93, 0, 0, 684, 139, 1, 0, 0, 0, 685, 686, 3, 152, 76, 0, 686, 687, 5, 61, 0, 0, 687, 688, 3, 142, 71, 0, 688, 141, 1, 0, 0, 0, 689, 732, 5, 72, 0, 0, 690, 691, 3, 150, 75, 0, 691, 692, 5, 101, 0, 0, 692, 732, 1, 0, 0, 0, 693, 732, 3, 148, 74, 0, 694, 732, 3, 150, 75, 0, 695, 732, 3, 144, 72, 0, 696, 732, 3, 56, 28, 0, 697, 732, 3, 152, 76, 0, 698, 699, 5, 97, 0, 0, 699, 704, 3, 146, 73, 0, 700, 701, 5, 62, 0, 0, 701, 703, 3, 146, 73, 0, 702, 700, 1, 0, 0, 0, 703, 706, 1, 0, 0, 0, 704, 702, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 707, 1, 0, 0, 0, 706, 704, 1, 0, 0, 0, 707, 708, 5, 98, 0, 0, 708, 732, 1, 0, 0, 0, 709, 710, 5, 97, 0, 0, 710, 715, 3, 144, 72, 0, 711, 712, 5, 62, 0, 0, 712, 714, 3, 144, 72, 0, 713, 711, 1, 0, 0, 0, 714, 717, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 718, 1, 0, 0, 0, 717, 715, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 732, 1, 0, 0, 0, 720, 721, 5, 97, 0, 0, 721, 726, 3, 152, 76, 0, 722, 723, 5, 62, 0, 0, 723, 725, 3, 152, 76, 0, 724, 722, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 729, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 730, 5, 98, 0, 0, 730, 732, 1, 0, 0, 0, 731, 689, 1, 0, 0, 0, 731, 690, 1, 0, 0, 0, 731, 693, 1, 0, 0, 0, 731, 694, 1, 0, 0, 0, 731, 695, 1, 0, 0, 0, 731, 696, 1, 0, 0, 0, 731, 697, 1, 0, 0, 0, 731, 698, 1, 0, 0, 0, 731, 709, 1, 0, 0, 0, 731, 720, 1, 0, 0, 0, 732, 143, 1, 0, 0, 0, 733, 734, 7, 6, 0, 0, 734, 145, 1, 0, 0, 0, 735, 738, 3, 148, 74, 0, 736, 738, 3, 150, 75, 0, 737, 735, 1, 0, 0, 0, 737, 736, 1, 0, 0, 0, 738, 147, 1, 0, 0, 0, 739, 741, 7, 4, 0, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 55, 0, 0, 743, 149, 1, 0, 0, 0, 744, 746, 7, 4, 0, 0, 745, 744, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 5, 54, 0, 0, 748, 151, 1, 0, 0, 0, 749, 750, 5, 53, 0, 0, 750, 153, 1, 0, 0, 0, 751, 752, 7, 7, 0, 0, 752, 155, 1, 0, 0, 0, 753, 754, 7, 8, 0, 0, 754, 755, 5, 114, 0, 0, 755, 756, 3, 158, 79, 0, 756, 757, 3, 160, 80, 0, 757, 157, 1, 0, 0, 0, 758, 759, 3, 28, 14, 0, 759, 159, 1, 0, 0, 0, 760, 761, 5, 74, 0, 0, 761, 766, 3, 162, 81, 0, 762, 763, 5, 62, 0, 0, 763, 765, 3, 162, 81, 0, 764, 762, 1, 0, 0, 0, 765, 768, 1, 0, 0, 0, 766, 764, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 161, 1, 0, 0, 0, 768, 766, 1, 0, 0, 0, 769, 770, 3, 128, 64, 0, 770, 163, 1, 0, 0, 0, 70, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 423, 444, 452, 455, 460, 473, 479, 486, 497, 511, 520, 530, 536, 552, 561, 569, 574, 582, 584, 589, 596, 601, 606, 616, 622, 630, 632, 643, 650, 661, 666, 668, 680, 704, 715, 726, 731, 737, 740, 745, 766] \ No newline at end of file +[4, 1, 139, 778, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 174, 8, 1, 10, 1, 12, 1, 177, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 185, 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, 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, 216, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 229, 8, 7, 10, 7, 12, 7, 232, 9, 7, 1, 8, 1, 8, 1, 8, 3, 8, 237, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 244, 8, 9, 10, 9, 12, 9, 247, 9, 9, 1, 10, 1, 10, 1, 10, 3, 10, 252, 8, 10, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 263, 8, 13, 10, 13, 12, 13, 266, 9, 13, 1, 13, 3, 13, 269, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 274, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 280, 8, 14, 3, 14, 282, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 294, 8, 18, 10, 18, 12, 18, 297, 9, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 3, 20, 304, 8, 20, 1, 20, 1, 20, 3, 20, 308, 8, 20, 1, 21, 1, 21, 1, 21, 5, 21, 313, 8, 21, 10, 21, 12, 21, 316, 9, 21, 1, 22, 1, 22, 1, 22, 3, 22, 321, 8, 22, 1, 23, 1, 23, 1, 23, 5, 23, 326, 8, 23, 10, 23, 12, 23, 329, 9, 23, 1, 24, 1, 24, 1, 24, 5, 24, 334, 8, 24, 10, 24, 12, 24, 337, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 342, 8, 25, 10, 25, 12, 25, 345, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 3, 27, 352, 8, 27, 1, 28, 1, 28, 3, 28, 356, 8, 28, 1, 29, 1, 29, 3, 29, 360, 8, 29, 1, 30, 1, 30, 1, 30, 3, 30, 365, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 374, 8, 32, 10, 32, 12, 32, 377, 9, 32, 1, 33, 1, 33, 3, 33, 381, 8, 33, 1, 33, 1, 33, 3, 33, 385, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 397, 8, 36, 10, 36, 12, 36, 400, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 410, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 416, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 428, 8, 41, 10, 41, 12, 41, 431, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 1, 44, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 3, 46, 451, 8, 46, 1, 46, 1, 46, 1, 46, 1, 46, 5, 46, 457, 8, 46, 10, 46, 12, 46, 460, 9, 46, 3, 46, 462, 8, 46, 1, 47, 1, 47, 1, 47, 3, 47, 467, 8, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 480, 8, 49, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 486, 8, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 3, 50, 493, 8, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 4, 53, 502, 8, 53, 11, 53, 12, 53, 503, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 5, 55, 516, 8, 55, 10, 55, 12, 55, 519, 9, 55, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 3, 56, 527, 8, 56, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 537, 8, 58, 1, 59, 1, 59, 1, 59, 1, 59, 3, 59, 543, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 559, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 566, 8, 61, 10, 61, 12, 61, 569, 9, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 576, 8, 61, 1, 61, 1, 61, 1, 61, 3, 61, 581, 8, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 5, 61, 589, 8, 61, 10, 61, 12, 61, 592, 9, 61, 1, 62, 1, 62, 3, 62, 596, 8, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 3, 62, 603, 8, 62, 1, 62, 1, 62, 1, 62, 3, 62, 608, 8, 62, 1, 63, 1, 63, 1, 63, 3, 63, 613, 8, 63, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 3, 64, 623, 8, 64, 1, 65, 1, 65, 1, 65, 1, 65, 3, 65, 629, 8, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 5, 65, 637, 8, 65, 10, 65, 12, 65, 640, 9, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 3, 66, 650, 8, 66, 1, 66, 1, 66, 1, 66, 5, 66, 655, 8, 66, 10, 66, 12, 66, 658, 9, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 5, 67, 666, 8, 67, 10, 67, 12, 67, 669, 9, 67, 1, 67, 1, 67, 3, 67, 673, 8, 67, 3, 67, 675, 8, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 69, 5, 69, 685, 8, 69, 10, 69, 12, 69, 688, 9, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 709, 8, 71, 10, 71, 12, 71, 712, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 720, 8, 71, 10, 71, 12, 71, 723, 9, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 1, 71, 5, 71, 731, 8, 71, 10, 71, 12, 71, 734, 9, 71, 1, 71, 1, 71, 3, 71, 738, 8, 71, 1, 72, 1, 72, 1, 73, 1, 73, 3, 73, 744, 8, 73, 1, 74, 3, 74, 747, 8, 74, 1, 74, 1, 74, 1, 75, 3, 75, 752, 8, 75, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 78, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 5, 80, 771, 8, 80, 10, 80, 12, 80, 774, 9, 80, 1, 81, 1, 81, 1, 81, 0, 5, 2, 110, 122, 130, 132, 82, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 0, 9, 2, 0, 53, 53, 107, 107, 1, 0, 101, 102, 2, 0, 57, 57, 63, 63, 2, 0, 66, 66, 69, 69, 1, 0, 87, 88, 1, 0, 89, 91, 2, 0, 65, 65, 78, 78, 2, 0, 80, 80, 82, 86, 2, 0, 22, 22, 24, 25, 810, 0, 164, 1, 0, 0, 0, 2, 167, 1, 0, 0, 0, 4, 184, 1, 0, 0, 0, 6, 215, 1, 0, 0, 0, 8, 217, 1, 0, 0, 0, 10, 220, 1, 0, 0, 0, 12, 222, 1, 0, 0, 0, 14, 225, 1, 0, 0, 0, 16, 236, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 248, 1, 0, 0, 0, 22, 253, 1, 0, 0, 0, 24, 256, 1, 0, 0, 0, 26, 259, 1, 0, 0, 0, 28, 281, 1, 0, 0, 0, 30, 283, 1, 0, 0, 0, 32, 285, 1, 0, 0, 0, 34, 287, 1, 0, 0, 0, 36, 289, 1, 0, 0, 0, 38, 298, 1, 0, 0, 0, 40, 301, 1, 0, 0, 0, 42, 309, 1, 0, 0, 0, 44, 317, 1, 0, 0, 0, 46, 322, 1, 0, 0, 0, 48, 330, 1, 0, 0, 0, 50, 338, 1, 0, 0, 0, 52, 346, 1, 0, 0, 0, 54, 351, 1, 0, 0, 0, 56, 355, 1, 0, 0, 0, 58, 359, 1, 0, 0, 0, 60, 364, 1, 0, 0, 0, 62, 366, 1, 0, 0, 0, 64, 369, 1, 0, 0, 0, 66, 378, 1, 0, 0, 0, 68, 386, 1, 0, 0, 0, 70, 389, 1, 0, 0, 0, 72, 392, 1, 0, 0, 0, 74, 409, 1, 0, 0, 0, 76, 411, 1, 0, 0, 0, 78, 417, 1, 0, 0, 0, 80, 421, 1, 0, 0, 0, 82, 424, 1, 0, 0, 0, 84, 432, 1, 0, 0, 0, 86, 436, 1, 0, 0, 0, 88, 439, 1, 0, 0, 0, 90, 443, 1, 0, 0, 0, 92, 446, 1, 0, 0, 0, 94, 466, 1, 0, 0, 0, 96, 470, 1, 0, 0, 0, 98, 475, 1, 0, 0, 0, 100, 481, 1, 0, 0, 0, 102, 494, 1, 0, 0, 0, 104, 497, 1, 0, 0, 0, 106, 501, 1, 0, 0, 0, 108, 505, 1, 0, 0, 0, 110, 509, 1, 0, 0, 0, 112, 526, 1, 0, 0, 0, 114, 528, 1, 0, 0, 0, 116, 530, 1, 0, 0, 0, 118, 538, 1, 0, 0, 0, 120, 548, 1, 0, 0, 0, 122, 580, 1, 0, 0, 0, 124, 607, 1, 0, 0, 0, 126, 609, 1, 0, 0, 0, 128, 622, 1, 0, 0, 0, 130, 628, 1, 0, 0, 0, 132, 649, 1, 0, 0, 0, 134, 659, 1, 0, 0, 0, 136, 678, 1, 0, 0, 0, 138, 680, 1, 0, 0, 0, 140, 691, 1, 0, 0, 0, 142, 737, 1, 0, 0, 0, 144, 739, 1, 0, 0, 0, 146, 743, 1, 0, 0, 0, 148, 746, 1, 0, 0, 0, 150, 751, 1, 0, 0, 0, 152, 755, 1, 0, 0, 0, 154, 757, 1, 0, 0, 0, 156, 759, 1, 0, 0, 0, 158, 764, 1, 0, 0, 0, 160, 766, 1, 0, 0, 0, 162, 775, 1, 0, 0, 0, 164, 165, 3, 2, 1, 0, 165, 166, 5, 0, 0, 1, 166, 1, 1, 0, 0, 0, 167, 168, 6, 1, -1, 0, 168, 169, 3, 4, 2, 0, 169, 175, 1, 0, 0, 0, 170, 171, 10, 1, 0, 0, 171, 172, 5, 52, 0, 0, 172, 174, 3, 6, 3, 0, 173, 170, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 3, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, 185, 3, 86, 43, 0, 179, 185, 3, 22, 11, 0, 180, 185, 3, 12, 6, 0, 181, 185, 3, 90, 45, 0, 182, 183, 4, 2, 1, 0, 183, 185, 3, 24, 12, 0, 184, 178, 1, 0, 0, 0, 184, 179, 1, 0, 0, 0, 184, 180, 1, 0, 0, 0, 184, 181, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 185, 5, 1, 0, 0, 0, 186, 216, 3, 38, 19, 0, 187, 216, 3, 8, 4, 0, 188, 216, 3, 68, 34, 0, 189, 216, 3, 62, 31, 0, 190, 216, 3, 40, 20, 0, 191, 216, 3, 64, 32, 0, 192, 216, 3, 70, 35, 0, 193, 216, 3, 72, 36, 0, 194, 216, 3, 76, 38, 0, 195, 216, 3, 78, 39, 0, 196, 216, 3, 92, 46, 0, 197, 216, 3, 80, 40, 0, 198, 216, 3, 156, 78, 0, 199, 216, 3, 100, 50, 0, 200, 216, 3, 118, 59, 0, 201, 202, 4, 3, 2, 0, 202, 216, 3, 98, 49, 0, 203, 204, 4, 3, 3, 0, 204, 216, 3, 96, 48, 0, 205, 206, 4, 3, 4, 0, 206, 216, 3, 102, 51, 0, 207, 208, 4, 3, 5, 0, 208, 216, 3, 104, 52, 0, 209, 210, 4, 3, 6, 0, 210, 216, 3, 116, 58, 0, 211, 212, 4, 3, 7, 0, 212, 216, 3, 114, 57, 0, 213, 214, 4, 3, 8, 0, 214, 216, 3, 120, 60, 0, 215, 186, 1, 0, 0, 0, 215, 187, 1, 0, 0, 0, 215, 188, 1, 0, 0, 0, 215, 189, 1, 0, 0, 0, 215, 190, 1, 0, 0, 0, 215, 191, 1, 0, 0, 0, 215, 192, 1, 0, 0, 0, 215, 193, 1, 0, 0, 0, 215, 194, 1, 0, 0, 0, 215, 195, 1, 0, 0, 0, 215, 196, 1, 0, 0, 0, 215, 197, 1, 0, 0, 0, 215, 198, 1, 0, 0, 0, 215, 199, 1, 0, 0, 0, 215, 200, 1, 0, 0, 0, 215, 201, 1, 0, 0, 0, 215, 203, 1, 0, 0, 0, 215, 205, 1, 0, 0, 0, 215, 207, 1, 0, 0, 0, 215, 209, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 216, 7, 1, 0, 0, 0, 217, 218, 5, 15, 0, 0, 218, 219, 3, 122, 61, 0, 219, 9, 1, 0, 0, 0, 220, 221, 3, 52, 26, 0, 221, 11, 1, 0, 0, 0, 222, 223, 5, 12, 0, 0, 223, 224, 3, 14, 7, 0, 224, 13, 1, 0, 0, 0, 225, 230, 3, 16, 8, 0, 226, 227, 5, 62, 0, 0, 227, 229, 3, 16, 8, 0, 228, 226, 1, 0, 0, 0, 229, 232, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 230, 231, 1, 0, 0, 0, 231, 15, 1, 0, 0, 0, 232, 230, 1, 0, 0, 0, 233, 234, 3, 46, 23, 0, 234, 235, 5, 58, 0, 0, 235, 237, 1, 0, 0, 0, 236, 233, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 239, 3, 122, 61, 0, 239, 17, 1, 0, 0, 0, 240, 245, 3, 20, 10, 0, 241, 242, 5, 62, 0, 0, 242, 244, 3, 20, 10, 0, 243, 241, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 19, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 251, 3, 46, 23, 0, 249, 250, 5, 58, 0, 0, 250, 252, 3, 122, 61, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 21, 1, 0, 0, 0, 253, 254, 5, 19, 0, 0, 254, 255, 3, 26, 13, 0, 255, 23, 1, 0, 0, 0, 256, 257, 5, 20, 0, 0, 257, 258, 3, 26, 13, 0, 258, 25, 1, 0, 0, 0, 259, 264, 3, 28, 14, 0, 260, 261, 5, 62, 0, 0, 261, 263, 3, 28, 14, 0, 262, 260, 1, 0, 0, 0, 263, 266, 1, 0, 0, 0, 264, 262, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 268, 1, 0, 0, 0, 266, 264, 1, 0, 0, 0, 267, 269, 3, 36, 18, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 27, 1, 0, 0, 0, 270, 271, 3, 30, 15, 0, 271, 272, 5, 61, 0, 0, 272, 274, 1, 0, 0, 0, 273, 270, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 282, 3, 34, 17, 0, 276, 279, 3, 34, 17, 0, 277, 278, 5, 60, 0, 0, 278, 280, 3, 32, 16, 0, 279, 277, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 282, 1, 0, 0, 0, 281, 273, 1, 0, 0, 0, 281, 276, 1, 0, 0, 0, 282, 29, 1, 0, 0, 0, 283, 284, 7, 0, 0, 0, 284, 31, 1, 0, 0, 0, 285, 286, 7, 0, 0, 0, 286, 33, 1, 0, 0, 0, 287, 288, 7, 0, 0, 0, 288, 35, 1, 0, 0, 0, 289, 290, 5, 106, 0, 0, 290, 295, 5, 107, 0, 0, 291, 292, 5, 62, 0, 0, 292, 294, 5, 107, 0, 0, 293, 291, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 37, 1, 0, 0, 0, 297, 295, 1, 0, 0, 0, 298, 299, 5, 9, 0, 0, 299, 300, 3, 14, 7, 0, 300, 39, 1, 0, 0, 0, 301, 303, 5, 14, 0, 0, 302, 304, 3, 42, 21, 0, 303, 302, 1, 0, 0, 0, 303, 304, 1, 0, 0, 0, 304, 307, 1, 0, 0, 0, 305, 306, 5, 59, 0, 0, 306, 308, 3, 14, 7, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 41, 1, 0, 0, 0, 309, 314, 3, 44, 22, 0, 310, 311, 5, 62, 0, 0, 311, 313, 3, 44, 22, 0, 312, 310, 1, 0, 0, 0, 313, 316, 1, 0, 0, 0, 314, 312, 1, 0, 0, 0, 314, 315, 1, 0, 0, 0, 315, 43, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 317, 320, 3, 16, 8, 0, 318, 319, 5, 15, 0, 0, 319, 321, 3, 122, 61, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 45, 1, 0, 0, 0, 322, 327, 3, 60, 30, 0, 323, 324, 5, 64, 0, 0, 324, 326, 3, 60, 30, 0, 325, 323, 1, 0, 0, 0, 326, 329, 1, 0, 0, 0, 327, 325, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 47, 1, 0, 0, 0, 329, 327, 1, 0, 0, 0, 330, 335, 3, 54, 27, 0, 331, 332, 5, 64, 0, 0, 332, 334, 3, 54, 27, 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, 49, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 338, 343, 3, 48, 24, 0, 339, 340, 5, 62, 0, 0, 340, 342, 3, 48, 24, 0, 341, 339, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 51, 1, 0, 0, 0, 345, 343, 1, 0, 0, 0, 346, 347, 7, 1, 0, 0, 347, 53, 1, 0, 0, 0, 348, 352, 5, 128, 0, 0, 349, 352, 3, 56, 28, 0, 350, 352, 3, 58, 29, 0, 351, 348, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 350, 1, 0, 0, 0, 352, 55, 1, 0, 0, 0, 353, 356, 5, 76, 0, 0, 354, 356, 5, 95, 0, 0, 355, 353, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 57, 1, 0, 0, 0, 357, 360, 5, 94, 0, 0, 358, 360, 5, 96, 0, 0, 359, 357, 1, 0, 0, 0, 359, 358, 1, 0, 0, 0, 360, 59, 1, 0, 0, 0, 361, 365, 3, 52, 26, 0, 362, 365, 3, 56, 28, 0, 363, 365, 3, 58, 29, 0, 364, 361, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 363, 1, 0, 0, 0, 365, 61, 1, 0, 0, 0, 366, 367, 5, 11, 0, 0, 367, 368, 3, 142, 71, 0, 368, 63, 1, 0, 0, 0, 369, 370, 5, 13, 0, 0, 370, 375, 3, 66, 33, 0, 371, 372, 5, 62, 0, 0, 372, 374, 3, 66, 33, 0, 373, 371, 1, 0, 0, 0, 374, 377, 1, 0, 0, 0, 375, 373, 1, 0, 0, 0, 375, 376, 1, 0, 0, 0, 376, 65, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 378, 380, 3, 122, 61, 0, 379, 381, 7, 2, 0, 0, 380, 379, 1, 0, 0, 0, 380, 381, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 383, 5, 73, 0, 0, 383, 385, 7, 3, 0, 0, 384, 382, 1, 0, 0, 0, 384, 385, 1, 0, 0, 0, 385, 67, 1, 0, 0, 0, 386, 387, 5, 29, 0, 0, 387, 388, 3, 50, 25, 0, 388, 69, 1, 0, 0, 0, 389, 390, 5, 28, 0, 0, 390, 391, 3, 50, 25, 0, 391, 71, 1, 0, 0, 0, 392, 393, 5, 32, 0, 0, 393, 398, 3, 74, 37, 0, 394, 395, 5, 62, 0, 0, 395, 397, 3, 74, 37, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 73, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 3, 48, 24, 0, 402, 403, 5, 132, 0, 0, 403, 404, 3, 48, 24, 0, 404, 410, 1, 0, 0, 0, 405, 406, 3, 48, 24, 0, 406, 407, 5, 58, 0, 0, 407, 408, 3, 48, 24, 0, 408, 410, 1, 0, 0, 0, 409, 401, 1, 0, 0, 0, 409, 405, 1, 0, 0, 0, 410, 75, 1, 0, 0, 0, 411, 412, 5, 8, 0, 0, 412, 413, 3, 132, 66, 0, 413, 415, 3, 152, 76, 0, 414, 416, 3, 82, 41, 0, 415, 414, 1, 0, 0, 0, 415, 416, 1, 0, 0, 0, 416, 77, 1, 0, 0, 0, 417, 418, 5, 10, 0, 0, 418, 419, 3, 132, 66, 0, 419, 420, 3, 152, 76, 0, 420, 79, 1, 0, 0, 0, 421, 422, 5, 27, 0, 0, 422, 423, 3, 46, 23, 0, 423, 81, 1, 0, 0, 0, 424, 429, 3, 84, 42, 0, 425, 426, 5, 62, 0, 0, 426, 428, 3, 84, 42, 0, 427, 425, 1, 0, 0, 0, 428, 431, 1, 0, 0, 0, 429, 427, 1, 0, 0, 0, 429, 430, 1, 0, 0, 0, 430, 83, 1, 0, 0, 0, 431, 429, 1, 0, 0, 0, 432, 433, 3, 52, 26, 0, 433, 434, 5, 58, 0, 0, 434, 435, 3, 142, 71, 0, 435, 85, 1, 0, 0, 0, 436, 437, 5, 6, 0, 0, 437, 438, 3, 88, 44, 0, 438, 87, 1, 0, 0, 0, 439, 440, 5, 97, 0, 0, 440, 441, 3, 2, 1, 0, 441, 442, 5, 98, 0, 0, 442, 89, 1, 0, 0, 0, 443, 444, 5, 33, 0, 0, 444, 445, 5, 136, 0, 0, 445, 91, 1, 0, 0, 0, 446, 447, 5, 5, 0, 0, 447, 450, 5, 38, 0, 0, 448, 449, 5, 74, 0, 0, 449, 451, 3, 48, 24, 0, 450, 448, 1, 0, 0, 0, 450, 451, 1, 0, 0, 0, 451, 461, 1, 0, 0, 0, 452, 453, 5, 79, 0, 0, 453, 458, 3, 94, 47, 0, 454, 455, 5, 62, 0, 0, 455, 457, 3, 94, 47, 0, 456, 454, 1, 0, 0, 0, 457, 460, 1, 0, 0, 0, 458, 456, 1, 0, 0, 0, 458, 459, 1, 0, 0, 0, 459, 462, 1, 0, 0, 0, 460, 458, 1, 0, 0, 0, 461, 452, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 93, 1, 0, 0, 0, 463, 464, 3, 48, 24, 0, 464, 465, 5, 58, 0, 0, 465, 467, 1, 0, 0, 0, 466, 463, 1, 0, 0, 0, 466, 467, 1, 0, 0, 0, 467, 468, 1, 0, 0, 0, 468, 469, 3, 48, 24, 0, 469, 95, 1, 0, 0, 0, 470, 471, 5, 26, 0, 0, 471, 472, 3, 28, 14, 0, 472, 473, 5, 74, 0, 0, 473, 474, 3, 50, 25, 0, 474, 97, 1, 0, 0, 0, 475, 476, 5, 16, 0, 0, 476, 479, 3, 42, 21, 0, 477, 478, 5, 59, 0, 0, 478, 480, 3, 14, 7, 0, 479, 477, 1, 0, 0, 0, 479, 480, 1, 0, 0, 0, 480, 99, 1, 0, 0, 0, 481, 482, 5, 4, 0, 0, 482, 485, 3, 46, 23, 0, 483, 484, 5, 74, 0, 0, 484, 486, 3, 46, 23, 0, 485, 483, 1, 0, 0, 0, 485, 486, 1, 0, 0, 0, 486, 492, 1, 0, 0, 0, 487, 488, 5, 132, 0, 0, 488, 489, 3, 46, 23, 0, 489, 490, 5, 62, 0, 0, 490, 491, 3, 46, 23, 0, 491, 493, 1, 0, 0, 0, 492, 487, 1, 0, 0, 0, 492, 493, 1, 0, 0, 0, 493, 101, 1, 0, 0, 0, 494, 495, 5, 30, 0, 0, 495, 496, 3, 50, 25, 0, 496, 103, 1, 0, 0, 0, 497, 498, 5, 21, 0, 0, 498, 499, 3, 106, 53, 0, 499, 105, 1, 0, 0, 0, 500, 502, 3, 108, 54, 0, 501, 500, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 501, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 107, 1, 0, 0, 0, 505, 506, 5, 99, 0, 0, 506, 507, 3, 110, 55, 0, 507, 508, 5, 100, 0, 0, 508, 109, 1, 0, 0, 0, 509, 510, 6, 55, -1, 0, 510, 511, 3, 112, 56, 0, 511, 517, 1, 0, 0, 0, 512, 513, 10, 1, 0, 0, 513, 514, 5, 52, 0, 0, 514, 516, 3, 112, 56, 0, 515, 512, 1, 0, 0, 0, 516, 519, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 111, 1, 0, 0, 0, 519, 517, 1, 0, 0, 0, 520, 527, 3, 38, 19, 0, 521, 527, 3, 8, 4, 0, 522, 527, 3, 62, 31, 0, 523, 527, 3, 40, 20, 0, 524, 527, 3, 64, 32, 0, 525, 527, 3, 76, 38, 0, 526, 520, 1, 0, 0, 0, 526, 521, 1, 0, 0, 0, 526, 522, 1, 0, 0, 0, 526, 523, 1, 0, 0, 0, 526, 524, 1, 0, 0, 0, 526, 525, 1, 0, 0, 0, 527, 113, 1, 0, 0, 0, 528, 529, 5, 31, 0, 0, 529, 115, 1, 0, 0, 0, 530, 531, 5, 17, 0, 0, 531, 532, 3, 142, 71, 0, 532, 533, 5, 74, 0, 0, 533, 536, 3, 18, 9, 0, 534, 535, 5, 79, 0, 0, 535, 537, 3, 60, 30, 0, 536, 534, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 117, 1, 0, 0, 0, 538, 542, 5, 7, 0, 0, 539, 540, 3, 46, 23, 0, 540, 541, 5, 58, 0, 0, 541, 543, 1, 0, 0, 0, 542, 539, 1, 0, 0, 0, 542, 543, 1, 0, 0, 0, 543, 544, 1, 0, 0, 0, 544, 545, 3, 132, 66, 0, 545, 546, 5, 79, 0, 0, 546, 547, 3, 60, 30, 0, 547, 119, 1, 0, 0, 0, 548, 549, 5, 18, 0, 0, 549, 550, 3, 148, 74, 0, 550, 121, 1, 0, 0, 0, 551, 552, 6, 61, -1, 0, 552, 553, 5, 71, 0, 0, 553, 581, 3, 122, 61, 8, 554, 581, 3, 128, 64, 0, 555, 581, 3, 124, 62, 0, 556, 558, 3, 128, 64, 0, 557, 559, 5, 71, 0, 0, 558, 557, 1, 0, 0, 0, 558, 559, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 561, 5, 67, 0, 0, 561, 562, 5, 99, 0, 0, 562, 567, 3, 128, 64, 0, 563, 564, 5, 62, 0, 0, 564, 566, 3, 128, 64, 0, 565, 563, 1, 0, 0, 0, 566, 569, 1, 0, 0, 0, 567, 565, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 570, 1, 0, 0, 0, 569, 567, 1, 0, 0, 0, 570, 571, 5, 100, 0, 0, 571, 581, 1, 0, 0, 0, 572, 573, 3, 128, 64, 0, 573, 575, 5, 68, 0, 0, 574, 576, 5, 71, 0, 0, 575, 574, 1, 0, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 1, 0, 0, 0, 577, 578, 5, 72, 0, 0, 578, 581, 1, 0, 0, 0, 579, 581, 3, 126, 63, 0, 580, 551, 1, 0, 0, 0, 580, 554, 1, 0, 0, 0, 580, 555, 1, 0, 0, 0, 580, 556, 1, 0, 0, 0, 580, 572, 1, 0, 0, 0, 580, 579, 1, 0, 0, 0, 581, 590, 1, 0, 0, 0, 582, 583, 10, 5, 0, 0, 583, 584, 5, 56, 0, 0, 584, 589, 3, 122, 61, 6, 585, 586, 10, 4, 0, 0, 586, 587, 5, 75, 0, 0, 587, 589, 3, 122, 61, 5, 588, 582, 1, 0, 0, 0, 588, 585, 1, 0, 0, 0, 589, 592, 1, 0, 0, 0, 590, 588, 1, 0, 0, 0, 590, 591, 1, 0, 0, 0, 591, 123, 1, 0, 0, 0, 592, 590, 1, 0, 0, 0, 593, 595, 3, 128, 64, 0, 594, 596, 5, 71, 0, 0, 595, 594, 1, 0, 0, 0, 595, 596, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 597, 598, 5, 70, 0, 0, 598, 599, 3, 152, 76, 0, 599, 608, 1, 0, 0, 0, 600, 602, 3, 128, 64, 0, 601, 603, 5, 71, 0, 0, 602, 601, 1, 0, 0, 0, 602, 603, 1, 0, 0, 0, 603, 604, 1, 0, 0, 0, 604, 605, 5, 77, 0, 0, 605, 606, 3, 152, 76, 0, 606, 608, 1, 0, 0, 0, 607, 593, 1, 0, 0, 0, 607, 600, 1, 0, 0, 0, 608, 125, 1, 0, 0, 0, 609, 612, 3, 46, 23, 0, 610, 611, 5, 60, 0, 0, 611, 613, 3, 10, 5, 0, 612, 610, 1, 0, 0, 0, 612, 613, 1, 0, 0, 0, 613, 614, 1, 0, 0, 0, 614, 615, 5, 61, 0, 0, 615, 616, 3, 142, 71, 0, 616, 127, 1, 0, 0, 0, 617, 623, 3, 130, 65, 0, 618, 619, 3, 130, 65, 0, 619, 620, 3, 154, 77, 0, 620, 621, 3, 130, 65, 0, 621, 623, 1, 0, 0, 0, 622, 617, 1, 0, 0, 0, 622, 618, 1, 0, 0, 0, 623, 129, 1, 0, 0, 0, 624, 625, 6, 65, -1, 0, 625, 629, 3, 132, 66, 0, 626, 627, 7, 4, 0, 0, 627, 629, 3, 130, 65, 3, 628, 624, 1, 0, 0, 0, 628, 626, 1, 0, 0, 0, 629, 638, 1, 0, 0, 0, 630, 631, 10, 2, 0, 0, 631, 632, 7, 5, 0, 0, 632, 637, 3, 130, 65, 3, 633, 634, 10, 1, 0, 0, 634, 635, 7, 4, 0, 0, 635, 637, 3, 130, 65, 2, 636, 630, 1, 0, 0, 0, 636, 633, 1, 0, 0, 0, 637, 640, 1, 0, 0, 0, 638, 636, 1, 0, 0, 0, 638, 639, 1, 0, 0, 0, 639, 131, 1, 0, 0, 0, 640, 638, 1, 0, 0, 0, 641, 642, 6, 66, -1, 0, 642, 650, 3, 142, 71, 0, 643, 650, 3, 46, 23, 0, 644, 650, 3, 134, 67, 0, 645, 646, 5, 99, 0, 0, 646, 647, 3, 122, 61, 0, 647, 648, 5, 100, 0, 0, 648, 650, 1, 0, 0, 0, 649, 641, 1, 0, 0, 0, 649, 643, 1, 0, 0, 0, 649, 644, 1, 0, 0, 0, 649, 645, 1, 0, 0, 0, 650, 656, 1, 0, 0, 0, 651, 652, 10, 1, 0, 0, 652, 653, 5, 60, 0, 0, 653, 655, 3, 10, 5, 0, 654, 651, 1, 0, 0, 0, 655, 658, 1, 0, 0, 0, 656, 654, 1, 0, 0, 0, 656, 657, 1, 0, 0, 0, 657, 133, 1, 0, 0, 0, 658, 656, 1, 0, 0, 0, 659, 660, 3, 136, 68, 0, 660, 674, 5, 99, 0, 0, 661, 675, 5, 89, 0, 0, 662, 667, 3, 122, 61, 0, 663, 664, 5, 62, 0, 0, 664, 666, 3, 122, 61, 0, 665, 663, 1, 0, 0, 0, 666, 669, 1, 0, 0, 0, 667, 665, 1, 0, 0, 0, 667, 668, 1, 0, 0, 0, 668, 672, 1, 0, 0, 0, 669, 667, 1, 0, 0, 0, 670, 671, 5, 62, 0, 0, 671, 673, 3, 138, 69, 0, 672, 670, 1, 0, 0, 0, 672, 673, 1, 0, 0, 0, 673, 675, 1, 0, 0, 0, 674, 661, 1, 0, 0, 0, 674, 662, 1, 0, 0, 0, 674, 675, 1, 0, 0, 0, 675, 676, 1, 0, 0, 0, 676, 677, 5, 100, 0, 0, 677, 135, 1, 0, 0, 0, 678, 679, 3, 60, 30, 0, 679, 137, 1, 0, 0, 0, 680, 681, 5, 92, 0, 0, 681, 686, 3, 140, 70, 0, 682, 683, 5, 62, 0, 0, 683, 685, 3, 140, 70, 0, 684, 682, 1, 0, 0, 0, 685, 688, 1, 0, 0, 0, 686, 684, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 689, 1, 0, 0, 0, 688, 686, 1, 0, 0, 0, 689, 690, 5, 93, 0, 0, 690, 139, 1, 0, 0, 0, 691, 692, 3, 152, 76, 0, 692, 693, 5, 61, 0, 0, 693, 694, 3, 142, 71, 0, 694, 141, 1, 0, 0, 0, 695, 738, 5, 72, 0, 0, 696, 697, 3, 150, 75, 0, 697, 698, 5, 101, 0, 0, 698, 738, 1, 0, 0, 0, 699, 738, 3, 148, 74, 0, 700, 738, 3, 150, 75, 0, 701, 738, 3, 144, 72, 0, 702, 738, 3, 56, 28, 0, 703, 738, 3, 152, 76, 0, 704, 705, 5, 97, 0, 0, 705, 710, 3, 146, 73, 0, 706, 707, 5, 62, 0, 0, 707, 709, 3, 146, 73, 0, 708, 706, 1, 0, 0, 0, 709, 712, 1, 0, 0, 0, 710, 708, 1, 0, 0, 0, 710, 711, 1, 0, 0, 0, 711, 713, 1, 0, 0, 0, 712, 710, 1, 0, 0, 0, 713, 714, 5, 98, 0, 0, 714, 738, 1, 0, 0, 0, 715, 716, 5, 97, 0, 0, 716, 721, 3, 144, 72, 0, 717, 718, 5, 62, 0, 0, 718, 720, 3, 144, 72, 0, 719, 717, 1, 0, 0, 0, 720, 723, 1, 0, 0, 0, 721, 719, 1, 0, 0, 0, 721, 722, 1, 0, 0, 0, 722, 724, 1, 0, 0, 0, 723, 721, 1, 0, 0, 0, 724, 725, 5, 98, 0, 0, 725, 738, 1, 0, 0, 0, 726, 727, 5, 97, 0, 0, 727, 732, 3, 152, 76, 0, 728, 729, 5, 62, 0, 0, 729, 731, 3, 152, 76, 0, 730, 728, 1, 0, 0, 0, 731, 734, 1, 0, 0, 0, 732, 730, 1, 0, 0, 0, 732, 733, 1, 0, 0, 0, 733, 735, 1, 0, 0, 0, 734, 732, 1, 0, 0, 0, 735, 736, 5, 98, 0, 0, 736, 738, 1, 0, 0, 0, 737, 695, 1, 0, 0, 0, 737, 696, 1, 0, 0, 0, 737, 699, 1, 0, 0, 0, 737, 700, 1, 0, 0, 0, 737, 701, 1, 0, 0, 0, 737, 702, 1, 0, 0, 0, 737, 703, 1, 0, 0, 0, 737, 704, 1, 0, 0, 0, 737, 715, 1, 0, 0, 0, 737, 726, 1, 0, 0, 0, 738, 143, 1, 0, 0, 0, 739, 740, 7, 6, 0, 0, 740, 145, 1, 0, 0, 0, 741, 744, 3, 148, 74, 0, 742, 744, 3, 150, 75, 0, 743, 741, 1, 0, 0, 0, 743, 742, 1, 0, 0, 0, 744, 147, 1, 0, 0, 0, 745, 747, 7, 4, 0, 0, 746, 745, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 1, 0, 0, 0, 748, 749, 5, 55, 0, 0, 749, 149, 1, 0, 0, 0, 750, 752, 7, 4, 0, 0, 751, 750, 1, 0, 0, 0, 751, 752, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 5, 54, 0, 0, 754, 151, 1, 0, 0, 0, 755, 756, 5, 53, 0, 0, 756, 153, 1, 0, 0, 0, 757, 758, 7, 7, 0, 0, 758, 155, 1, 0, 0, 0, 759, 760, 7, 8, 0, 0, 760, 761, 5, 114, 0, 0, 761, 762, 3, 158, 79, 0, 762, 763, 3, 160, 80, 0, 763, 157, 1, 0, 0, 0, 764, 765, 3, 28, 14, 0, 765, 159, 1, 0, 0, 0, 766, 767, 5, 74, 0, 0, 767, 772, 3, 162, 81, 0, 768, 769, 5, 62, 0, 0, 769, 771, 3, 162, 81, 0, 770, 768, 1, 0, 0, 0, 771, 774, 1, 0, 0, 0, 772, 770, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 161, 1, 0, 0, 0, 774, 772, 1, 0, 0, 0, 775, 776, 3, 128, 64, 0, 776, 163, 1, 0, 0, 0, 71, 175, 184, 215, 230, 236, 245, 251, 264, 268, 273, 279, 281, 295, 303, 307, 314, 320, 327, 335, 343, 351, 355, 359, 364, 375, 380, 384, 398, 409, 415, 429, 450, 458, 461, 466, 479, 485, 492, 503, 517, 526, 536, 542, 558, 567, 575, 580, 588, 590, 595, 602, 607, 612, 622, 628, 636, 638, 649, 656, 667, 672, 674, 686, 710, 721, 732, 737, 743, 746, 751, 772] \ 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 6f3c676e44abc..b3bb8d07d7986 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 @@ -2946,6 +2946,7 @@ public List qualifiedNamePattern() { public QualifiedNamePatternContext qualifiedNamePattern(int i) { return getRuleContext(QualifiedNamePatternContext.class,i); } + public TerminalNode ASSIGN() { return getToken(EsqlBaseParser.ASSIGN, 0); } @SuppressWarnings("this-escape") public RenameClauseContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); @@ -2970,14 +2971,31 @@ public final RenameClauseContext renameClause() throws RecognitionException { RenameClauseContext _localctx = new RenameClauseContext(_ctx, getState()); enterRule(_localctx, 74, RULE_renameClause); try { - enterOuterAlt(_localctx, 1); - { - setState(401); - ((RenameClauseContext)_localctx).oldName = qualifiedNamePattern(); - setState(402); - match(AS); - setState(403); - ((RenameClauseContext)_localctx).newName = qualifiedNamePattern(); + setState(409); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,28,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(401); + ((RenameClauseContext)_localctx).oldName = qualifiedNamePattern(); + setState(402); + match(AS); + setState(403); + ((RenameClauseContext)_localctx).newName = qualifiedNamePattern(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(405); + ((RenameClauseContext)_localctx).newName = qualifiedNamePattern(); + setState(406); + match(ASSIGN); + setState(407); + ((RenameClauseContext)_localctx).oldName = qualifiedNamePattern(); + } + break; } } catch (RecognitionException re) { @@ -3029,18 +3047,18 @@ public final DissectCommandContext dissectCommand() throws RecognitionException try { enterOuterAlt(_localctx, 1); { - setState(405); + setState(411); match(DISSECT); - setState(406); + setState(412); primaryExpression(0); - setState(407); + setState(413); string(); - setState(409); + setState(415); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,28,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,29,_ctx) ) { case 1: { - setState(408); + setState(414); commandOptions(); } break; @@ -3093,11 +3111,11 @@ public final GrokCommandContext grokCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(411); + setState(417); match(GROK); - setState(412); + setState(418); primaryExpression(0); - setState(413); + setState(419); string(); } } @@ -3144,9 +3162,9 @@ public final MvExpandCommandContext mvExpandCommand() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(415); + setState(421); match(MV_EXPAND); - setState(416); + setState(422); qualifiedName(); } } @@ -3200,25 +3218,25 @@ public final CommandOptionsContext commandOptions() throws RecognitionException int _alt; enterOuterAlt(_localctx, 1); { - setState(418); + setState(424); commandOption(); - setState(423); + setState(429); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,29,_ctx); + _alt = getInterpreter().adaptivePredict(_input,30,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(419); + setState(425); match(COMMA); - setState(420); + setState(426); commandOption(); } } } - setState(425); + setState(431); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,29,_ctx); + _alt = getInterpreter().adaptivePredict(_input,30,_ctx); } } } @@ -3268,11 +3286,11 @@ public final CommandOptionContext commandOption() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(426); + setState(432); identifier(); - setState(427); + setState(433); match(ASSIGN); - setState(428); + setState(434); constant(); } } @@ -3319,9 +3337,9 @@ public final ExplainCommandContext explainCommand() throws RecognitionException try { enterOuterAlt(_localctx, 1); { - setState(430); + setState(436); match(EXPLAIN); - setState(431); + setState(437); subqueryExpression(); } } @@ -3369,11 +3387,11 @@ public final SubqueryExpressionContext subqueryExpression() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(433); + setState(439); match(OPENING_BRACKET); - setState(434); + setState(440); query(0); - setState(435); + setState(441); match(CLOSING_BRACKET); } } @@ -3430,9 +3448,9 @@ public final ShowCommandContext showCommand() throws RecognitionException { _localctx = new ShowInfoContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(437); + setState(443); match(SHOW); - setState(438); + setState(444); match(INFO); } } @@ -3495,48 +3513,48 @@ public final EnrichCommandContext enrichCommand() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(440); + setState(446); match(ENRICH); - setState(441); + setState(447); ((EnrichCommandContext)_localctx).policyName = match(ENRICH_POLICY_NAME); - setState(444); + setState(450); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,30,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,31,_ctx) ) { case 1: { - setState(442); + setState(448); match(ON); - setState(443); + setState(449); ((EnrichCommandContext)_localctx).matchField = qualifiedNamePattern(); } break; } - setState(455); + setState(461); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,32,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,33,_ctx) ) { case 1: { - setState(446); + setState(452); match(WITH); - setState(447); + setState(453); enrichWithClause(); - setState(452); + setState(458); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,31,_ctx); + _alt = getInterpreter().adaptivePredict(_input,32,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(448); + setState(454); match(COMMA); - setState(449); + setState(455); enrichWithClause(); } } } - setState(454); + setState(460); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,31,_ctx); + _alt = getInterpreter().adaptivePredict(_input,32,_ctx); } } break; @@ -3591,19 +3609,19 @@ public final EnrichWithClauseContext enrichWithClause() throws RecognitionExcept try { enterOuterAlt(_localctx, 1); { - setState(460); + setState(466); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,33,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,34,_ctx) ) { case 1: { - setState(457); + setState(463); ((EnrichWithClauseContext)_localctx).newName = qualifiedNamePattern(); - setState(458); + setState(464); match(ASSIGN); } break; } - setState(462); + setState(468); ((EnrichWithClauseContext)_localctx).enrichField = qualifiedNamePattern(); } } @@ -3656,13 +3674,13 @@ public final LookupCommandContext lookupCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(464); + setState(470); match(DEV_LOOKUP); - setState(465); + setState(471); ((LookupCommandContext)_localctx).tableName = indexPattern(); - setState(466); + setState(472); match(ON); - setState(467); + setState(473); ((LookupCommandContext)_localctx).matchFields = qualifiedNamePatterns(); } } @@ -3715,18 +3733,18 @@ public final InlinestatsCommandContext inlinestatsCommand() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(469); + setState(475); match(DEV_INLINESTATS); - setState(470); + setState(476); ((InlinestatsCommandContext)_localctx).stats = aggFields(); - setState(473); + setState(479); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,34,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,35,_ctx) ) { case 1: { - setState(471); + setState(477); match(BY); - setState(472); + setState(478); ((InlinestatsCommandContext)_localctx).grouping = fields(); } break; @@ -3786,34 +3804,34 @@ public final ChangePointCommandContext changePointCommand() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(475); + setState(481); match(CHANGE_POINT); - setState(476); + setState(482); ((ChangePointCommandContext)_localctx).value = qualifiedName(); - setState(479); + setState(485); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,35,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,36,_ctx) ) { case 1: { - setState(477); + setState(483); match(ON); - setState(478); + setState(484); ((ChangePointCommandContext)_localctx).key = qualifiedName(); } break; } - setState(486); + setState(492); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,36,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,37,_ctx) ) { case 1: { - setState(481); + setState(487); match(AS); - setState(482); + setState(488); ((ChangePointCommandContext)_localctx).targetType = qualifiedName(); - setState(483); + setState(489); match(COMMA); - setState(484); + setState(490); ((ChangePointCommandContext)_localctx).targetPvalue = qualifiedName(); } break; @@ -3863,9 +3881,9 @@ public final InsistCommandContext insistCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(488); + setState(494); match(DEV_INSIST); - setState(489); + setState(495); qualifiedNamePatterns(); } } @@ -3912,9 +3930,9 @@ public final ForkCommandContext forkCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(491); + setState(497); match(DEV_FORK); - setState(492); + setState(498); forkSubQueries(); } } @@ -3964,7 +3982,7 @@ public final ForkSubQueriesContext forkSubQueries() throws RecognitionException int _alt; enterOuterAlt(_localctx, 1); { - setState(495); + setState(501); _errHandler.sync(this); _alt = 1; do { @@ -3972,7 +3990,7 @@ public final ForkSubQueriesContext forkSubQueries() throws RecognitionException case 1: { { - setState(494); + setState(500); forkSubQuery(); } } @@ -3980,9 +3998,9 @@ public final ForkSubQueriesContext forkSubQueries() throws RecognitionException default: throw new NoViableAltException(this); } - setState(497); + setState(503); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,37,_ctx); + _alt = getInterpreter().adaptivePredict(_input,38,_ctx); } while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ); } } @@ -4030,11 +4048,11 @@ public final ForkSubQueryContext forkSubQuery() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(499); + setState(505); match(LP); - setState(500); + setState(506); forkSubQueryCommand(0); - setState(501); + setState(507); match(RP); } } @@ -4130,13 +4148,13 @@ private ForkSubQueryCommandContext forkSubQueryCommand(int _p) throws Recognitio _ctx = _localctx; _prevctx = _localctx; - setState(504); + setState(510); forkSubQueryProcessingCommand(); } _ctx.stop = _input.LT(-1); - setState(511); + setState(517); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,38,_ctx); + _alt = getInterpreter().adaptivePredict(_input,39,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); @@ -4145,18 +4163,18 @@ private ForkSubQueryCommandContext forkSubQueryCommand(int _p) throws Recognitio { _localctx = new CompositeForkSubQueryContext(new ForkSubQueryCommandContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_forkSubQueryCommand); - setState(506); + setState(512); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(507); + setState(513); match(PIPE); - setState(508); + setState(514); forkSubQueryProcessingCommand(); } } } - setState(513); + setState(519); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,38,_ctx); + _alt = getInterpreter().adaptivePredict(_input,39,_ctx); } } } @@ -4215,48 +4233,48 @@ public final ForkSubQueryProcessingCommandContext forkSubQueryProcessingCommand( ForkSubQueryProcessingCommandContext _localctx = new ForkSubQueryProcessingCommandContext(_ctx, getState()); enterRule(_localctx, 112, RULE_forkSubQueryProcessingCommand); try { - setState(520); + setState(526); _errHandler.sync(this); switch (_input.LA(1)) { case EVAL: enterOuterAlt(_localctx, 1); { - setState(514); + setState(520); evalCommand(); } break; case WHERE: enterOuterAlt(_localctx, 2); { - setState(515); + setState(521); whereCommand(); } break; case LIMIT: enterOuterAlt(_localctx, 3); { - setState(516); + setState(522); limitCommand(); } break; case STATS: enterOuterAlt(_localctx, 4); { - setState(517); + setState(523); statsCommand(); } break; case SORT: enterOuterAlt(_localctx, 5); { - setState(518); + setState(524); sortCommand(); } break; case DISSECT: enterOuterAlt(_localctx, 6); { - setState(519); + setState(525); dissectCommand(); } break; @@ -4304,7 +4322,7 @@ public final RrfCommandContext rrfCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(522); + setState(528); match(DEV_RRF); } } @@ -4361,22 +4379,22 @@ public final RerankCommandContext rerankCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(524); + setState(530); match(DEV_RERANK); - setState(525); + setState(531); ((RerankCommandContext)_localctx).queryText = constant(); - setState(526); + setState(532); match(ON); - setState(527); + setState(533); rerankFields(); - setState(530); + setState(536); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,40,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,41,_ctx) ) { case 1: { - setState(528); + setState(534); match(WITH); - setState(529); + setState(535); ((RerankCommandContext)_localctx).inferenceId = identifierOrParameter(); } break; @@ -4437,25 +4455,25 @@ public final CompletionCommandContext completionCommand() throws RecognitionExce try { enterOuterAlt(_localctx, 1); { - setState(532); + setState(538); match(COMPLETION); - setState(536); + setState(542); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,41,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,42,_ctx) ) { case 1: { - setState(533); + setState(539); ((CompletionCommandContext)_localctx).targetField = qualifiedName(); - setState(534); + setState(540); match(ASSIGN); } break; } - setState(538); + setState(544); ((CompletionCommandContext)_localctx).prompt = primaryExpression(0); - setState(539); + setState(545); match(WITH); - setState(540); + setState(546); ((CompletionCommandContext)_localctx).inferenceId = identifierOrParameter(); } } @@ -4503,9 +4521,9 @@ public final SampleCommandContext sampleCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(542); + setState(548); match(DEV_SAMPLE); - setState(543); + setState(549); ((SampleCommandContext)_localctx).probability = decimalValue(); } } @@ -4721,18 +4739,18 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(574); + setState(580); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: { _localctx = new LogicalNotContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(546); + setState(552); match(NOT); - setState(547); + setState(553); booleanExpression(8); } break; @@ -4741,7 +4759,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(548); + setState(554); valueExpression(); } break; @@ -4750,7 +4768,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(549); + setState(555); regexBooleanExpression(); } break; @@ -4759,41 +4777,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(550); + setState(556); valueExpression(); - setState(552); + setState(558); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(551); + setState(557); match(NOT); } } - setState(554); + setState(560); match(IN); - setState(555); + setState(561); match(LP); - setState(556); + setState(562); valueExpression(); - setState(561); + setState(567); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(557); + setState(563); match(COMMA); - setState(558); + setState(564); valueExpression(); } } - setState(563); + setState(569); _errHandler.sync(this); _la = _input.LA(1); } - setState(564); + setState(570); match(RP); } break; @@ -4802,21 +4820,21 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(566); + setState(572); valueExpression(); - setState(567); + setState(573); match(IS); - setState(569); + setState(575); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(568); + setState(574); match(NOT); } } - setState(571); + setState(577); match(NULL); } break; @@ -4825,33 +4843,33 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new MatchExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(573); + setState(579); matchBooleanExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(584); + setState(590); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,47,_ctx); + _alt = getInterpreter().adaptivePredict(_input,48,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(582); + setState(588); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,47,_ctx) ) { case 1: { _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(576); + setState(582); if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(577); + setState(583); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(578); + setState(584); ((LogicalBinaryContext)_localctx).right = booleanExpression(6); } break; @@ -4860,20 +4878,20 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); ((LogicalBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_booleanExpression); - setState(579); + setState(585); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(580); + setState(586); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(581); + setState(587); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; } } } - setState(586); + setState(592); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,47,_ctx); + _alt = getInterpreter().adaptivePredict(_input,48,_ctx); } } } @@ -4926,48 +4944,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 124, RULE_regexBooleanExpression); int _la; try { - setState(601); + setState(607); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,50,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,51,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(587); + setState(593); valueExpression(); - setState(589); + setState(595); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(588); + setState(594); match(NOT); } } - setState(591); + setState(597); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(592); + setState(598); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(594); + setState(600); valueExpression(); - setState(596); + setState(602); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(595); + setState(601); match(NOT); } } - setState(598); + setState(604); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(599); + setState(605); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -5027,23 +5045,23 @@ public final MatchBooleanExpressionContext matchBooleanExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(603); + setState(609); ((MatchBooleanExpressionContext)_localctx).fieldExp = qualifiedName(); - setState(606); + setState(612); _errHandler.sync(this); _la = _input.LA(1); if (_la==CAST_OP) { { - setState(604); + setState(610); match(CAST_OP); - setState(605); + setState(611); ((MatchBooleanExpressionContext)_localctx).fieldType = dataType(); } } - setState(608); + setState(614); match(COLON); - setState(609); + setState(615); ((MatchBooleanExpressionContext)_localctx).matchQuery = constant(); } } @@ -5127,14 +5145,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 128, RULE_valueExpression); try { - setState(616); + setState(622); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(611); + setState(617); operatorExpression(0); } break; @@ -5142,11 +5160,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(612); + setState(618); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(613); + setState(619); comparisonOperator(); - setState(614); + setState(620); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -5271,16 +5289,16 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(622); + setState(628); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { case 1: { _localctx = new OperatorExpressionDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(619); + setState(625); primaryExpression(0); } break; @@ -5289,7 +5307,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(620); + setState(626); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5300,31 +5318,31 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(621); + setState(627); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(632); + setState(638); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,55,_ctx); + _alt = getInterpreter().adaptivePredict(_input,56,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(630); + setState(636); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,55,_ctx) ) { case 1: { _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState)); ((ArithmeticBinaryContext)_localctx).left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_operatorExpression); - setState(624); + setState(630); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(625); + setState(631); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 89)) & ~0x3f) == 0 && ((1L << (_la - 89)) & 7L) != 0)) ) { @@ -5335,7 +5353,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(626); + setState(632); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -5344,9 +5362,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(627); + setState(633); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(628); + setState(634); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -5357,16 +5375,16 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(629); + setState(635); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(634); + setState(640); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,55,_ctx); + _alt = getInterpreter().adaptivePredict(_input,56,_ctx); } } } @@ -5522,16 +5540,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(643); + setState(649); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,56,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,57,_ctx) ) { case 1: { _localctx = new ConstantDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(636); + setState(642); constant(); } break; @@ -5540,7 +5558,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(637); + setState(643); qualifiedName(); } break; @@ -5549,7 +5567,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(638); + setState(644); functionExpression(); } break; @@ -5558,19 +5576,19 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(639); + setState(645); match(LP); - setState(640); + setState(646); booleanExpression(0); - setState(641); + setState(647); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(650); + setState(656); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,57,_ctx); + _alt = getInterpreter().adaptivePredict(_input,58,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( _parseListeners!=null ) triggerExitRuleEvent(); @@ -5579,18 +5597,18 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(645); + setState(651); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(646); + setState(652); match(CAST_OP); - setState(647); + setState(653); dataType(); } } } - setState(652); + setState(658); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,57,_ctx); + _alt = getInterpreter().adaptivePredict(_input,58,_ctx); } } } @@ -5654,16 +5672,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx int _alt; enterOuterAlt(_localctx, 1); { - setState(653); + setState(659); functionName(); - setState(654); + setState(660); match(LP); - setState(668); + setState(674); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(655); + setState(661); match(ASTERISK); } break; @@ -5686,34 +5704,34 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(656); + setState(662); booleanExpression(0); - setState(661); + setState(667); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,58,_ctx); + _alt = getInterpreter().adaptivePredict(_input,59,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(657); + setState(663); match(COMMA); - setState(658); + setState(664); booleanExpression(0); } } } - setState(663); + setState(669); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,58,_ctx); + _alt = getInterpreter().adaptivePredict(_input,59,_ctx); } - setState(666); + setState(672); _errHandler.sync(this); _la = _input.LA(1); if (_la==COMMA) { { - setState(664); + setState(670); match(COMMA); - setState(665); + setState(671); mapExpression(); } } @@ -5726,7 +5744,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(670); + setState(676); match(RP); } } @@ -5772,7 +5790,7 @@ public final FunctionNameContext functionName() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(672); + setState(678); identifierOrParameter(); } } @@ -5828,27 +5846,27 @@ public final MapExpressionContext mapExpression() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(674); + setState(680); match(LEFT_BRACES); - setState(675); + setState(681); entryExpression(); - setState(680); + setState(686); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(676); + setState(682); match(COMMA); - setState(677); + setState(683); entryExpression(); } } - setState(682); + setState(688); _errHandler.sync(this); _la = _input.LA(1); } - setState(683); + setState(689); match(RIGHT_BRACES); } } @@ -5900,11 +5918,11 @@ public final EntryExpressionContext entryExpression() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(685); + setState(691); ((EntryExpressionContext)_localctx).key = string(); - setState(686); + setState(692); match(COLON); - setState(687); + setState(693); ((EntryExpressionContext)_localctx).value = constant(); } } @@ -6175,14 +6193,14 @@ public final ConstantContext constant() throws RecognitionException { enterRule(_localctx, 142, RULE_constant); int _la; try { - setState(731); + setState(737); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,65,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(689); + setState(695); match(NULL); } break; @@ -6190,9 +6208,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(690); + setState(696); integerValue(); - setState(691); + setState(697); match(UNQUOTED_IDENTIFIER); } break; @@ -6200,7 +6218,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(693); + setState(699); decimalValue(); } break; @@ -6208,7 +6226,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(694); + setState(700); integerValue(); } break; @@ -6216,7 +6234,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(695); + setState(701); booleanValue(); } break; @@ -6224,7 +6242,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParameterContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(696); + setState(702); parameter(); } break; @@ -6232,7 +6250,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(697); + setState(703); string(); } break; @@ -6240,27 +6258,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(698); + setState(704); match(OPENING_BRACKET); - setState(699); + setState(705); numericValue(); - setState(704); + setState(710); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(700); + setState(706); match(COMMA); - setState(701); + setState(707); numericValue(); } } - setState(706); + setState(712); _errHandler.sync(this); _la = _input.LA(1); } - setState(707); + setState(713); match(CLOSING_BRACKET); } break; @@ -6268,27 +6286,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(709); + setState(715); match(OPENING_BRACKET); - setState(710); + setState(716); booleanValue(); - setState(715); + setState(721); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(711); + setState(717); match(COMMA); - setState(712); + setState(718); booleanValue(); } } - setState(717); + setState(723); _errHandler.sync(this); _la = _input.LA(1); } - setState(718); + setState(724); match(CLOSING_BRACKET); } break; @@ -6296,27 +6314,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(720); + setState(726); match(OPENING_BRACKET); - setState(721); + setState(727); string(); - setState(726); + setState(732); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(722); + setState(728); match(COMMA); - setState(723); + setState(729); string(); } } - setState(728); + setState(734); _errHandler.sync(this); _la = _input.LA(1); } - setState(729); + setState(735); match(CLOSING_BRACKET); } break; @@ -6364,7 +6382,7 @@ public final BooleanValueContext booleanValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(733); + setState(739); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -6419,20 +6437,20 @@ public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); enterRule(_localctx, 146, RULE_numericValue); try { - setState(737); + setState(743); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,66,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,67,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(735); + setState(741); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(736); + setState(742); integerValue(); } break; @@ -6481,12 +6499,12 @@ public final DecimalValueContext decimalValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(740); + setState(746); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(739); + setState(745); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6499,7 +6517,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(742); + setState(748); match(DECIMAL_LITERAL); } } @@ -6546,12 +6564,12 @@ public final IntegerValueContext integerValue() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(745); + setState(751); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(744); + setState(750); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -6564,7 +6582,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(747); + setState(753); match(INTEGER_LITERAL); } } @@ -6608,7 +6626,7 @@ public final StringContext string() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(749); + setState(755); match(QUOTED_STRING); } } @@ -6658,7 +6676,7 @@ public final ComparisonOperatorContext comparisonOperator() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(751); + setState(757); _la = _input.LA(1); if ( !(((((_la - 80)) & ~0x3f) == 0 && ((1L << (_la - 80)) & 125L) != 0)) ) { _errHandler.recoverInline(this); @@ -6721,7 +6739,7 @@ public final JoinCommandContext joinCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(753); + setState(759); ((JoinCommandContext)_localctx).type = _input.LT(1); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 54525952L) != 0)) ) { @@ -6732,11 +6750,11 @@ public final JoinCommandContext joinCommand() throws RecognitionException { _errHandler.reportMatch(this); consume(); } - setState(754); + setState(760); match(JOIN); - setState(755); + setState(761); joinTarget(); - setState(756); + setState(762); joinCondition(); } } @@ -6783,7 +6801,7 @@ public final JoinTargetContext joinTarget() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(758); + setState(764); ((JoinTargetContext)_localctx).index = indexPattern(); } } @@ -6838,27 +6856,27 @@ public final JoinConditionContext joinCondition() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(760); + setState(766); match(ON); - setState(761); + setState(767); joinPredicate(); - setState(766); + setState(772); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,69,_ctx); + _alt = getInterpreter().adaptivePredict(_input,70,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(762); + setState(768); match(COMMA); - setState(763); + setState(769); joinPredicate(); } } } - setState(768); + setState(774); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,69,_ctx); + _alt = getInterpreter().adaptivePredict(_input,70,_ctx); } } } @@ -6904,7 +6922,7 @@ public final JoinPredicateContext joinPredicate() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(769); + setState(775); valueExpression(); } } @@ -7005,7 +7023,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001\u008b\u0304\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001"+ + "\u0004\u0001\u008b\u030a\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"+ @@ -7061,425 +7079,429 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in " \f \u0179\t \u0001!\u0001!\u0003!\u017d\b!\u0001!\u0001!\u0003!\u0181"+ "\b!\u0001\"\u0001\"\u0001\"\u0001#\u0001#\u0001#\u0001$\u0001$\u0001$"+ "\u0001$\u0005$\u018d\b$\n$\f$\u0190\t$\u0001%\u0001%\u0001%\u0001%\u0001"+ - "&\u0001&\u0001&\u0001&\u0003&\u019a\b&\u0001\'\u0001\'\u0001\'\u0001\'"+ - "\u0001(\u0001(\u0001(\u0001)\u0001)\u0001)\u0005)\u01a6\b)\n)\f)\u01a9"+ - "\t)\u0001*\u0001*\u0001*\u0001*\u0001+\u0001+\u0001+\u0001,\u0001,\u0001"+ - ",\u0001,\u0001-\u0001-\u0001-\u0001.\u0001.\u0001.\u0001.\u0003.\u01bd"+ - "\b.\u0001.\u0001.\u0001.\u0001.\u0005.\u01c3\b.\n.\f.\u01c6\t.\u0003."+ - "\u01c8\b.\u0001/\u0001/\u0001/\u0003/\u01cd\b/\u0001/\u0001/\u00010\u0001"+ - "0\u00010\u00010\u00010\u00011\u00011\u00011\u00011\u00031\u01da\b1\u0001"+ - "2\u00012\u00012\u00012\u00032\u01e0\b2\u00012\u00012\u00012\u00012\u0001"+ - "2\u00032\u01e7\b2\u00013\u00013\u00013\u00014\u00014\u00014\u00015\u0004"+ - "5\u01f0\b5\u000b5\f5\u01f1\u00016\u00016\u00016\u00016\u00017\u00017\u0001"+ - "7\u00017\u00017\u00017\u00057\u01fe\b7\n7\f7\u0201\t7\u00018\u00018\u0001"+ - "8\u00018\u00018\u00018\u00038\u0209\b8\u00019\u00019\u0001:\u0001:\u0001"+ - ":\u0001:\u0001:\u0001:\u0003:\u0213\b:\u0001;\u0001;\u0001;\u0001;\u0003"+ - ";\u0219\b;\u0001;\u0001;\u0001;\u0001;\u0001<\u0001<\u0001<\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0229\b=\u0001=\u0001=\u0001"+ - "=\u0001=\u0001=\u0005=\u0230\b=\n=\f=\u0233\t=\u0001=\u0001=\u0001=\u0001"+ - "=\u0001=\u0003=\u023a\b=\u0001=\u0001=\u0001=\u0003=\u023f\b=\u0001=\u0001"+ - "=\u0001=\u0001=\u0001=\u0001=\u0005=\u0247\b=\n=\f=\u024a\t=\u0001>\u0001"+ - ">\u0003>\u024e\b>\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u0255\b>\u0001"+ - ">\u0001>\u0001>\u0003>\u025a\b>\u0001?\u0001?\u0001?\u0003?\u025f\b?\u0001"+ - "?\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u0269\b@\u0001"+ - "A\u0001A\u0001A\u0001A\u0003A\u026f\bA\u0001A\u0001A\u0001A\u0001A\u0001"+ - "A\u0001A\u0005A\u0277\bA\nA\fA\u027a\tA\u0001B\u0001B\u0001B\u0001B\u0001"+ - "B\u0001B\u0001B\u0001B\u0003B\u0284\bB\u0001B\u0001B\u0001B\u0005B\u0289"+ - "\bB\nB\fB\u028c\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u0294"+ - "\bC\nC\fC\u0297\tC\u0001C\u0001C\u0003C\u029b\bC\u0003C\u029d\bC\u0001"+ - "C\u0001C\u0001D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02a7\bE\nE"+ - "\fE\u02aa\tE\u0001E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001"+ - "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02bf\bG\nG\fG\u02c2\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02ca\bG\nG\fG\u02cd\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001"+ - "G\u0005G\u02d5\bG\nG\fG\u02d8\tG\u0001G\u0001G\u0003G\u02dc\bG\u0001H"+ - "\u0001H\u0001I\u0001I\u0003I\u02e2\bI\u0001J\u0003J\u02e5\bJ\u0001J\u0001"+ - "J\u0001K\u0003K\u02ea\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ - "N\u0001N\u0001N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ - "P\u0005P\u02fd\bP\nP\fP\u0300\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002"+ - "nz\u0082\u0084R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014"+ - "\u0016\u0018\u001a\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh"+ - "jlnprtvxz|~\u0080\u0082\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092"+ - "\u0094\u0096\u0098\u009a\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u00005"+ - "5kk\u0001\u0000ef\u0002\u000099??\u0002\u0000BBEE\u0001\u0000WX\u0001"+ - "\u0000Y[\u0002\u0000AANN\u0002\u0000PPRV\u0002\u0000\u0016\u0016\u0018"+ - "\u0019\u0323\u0000\u00a4\u0001\u0000\u0000\u0000\u0002\u00a7\u0001\u0000"+ - "\u0000\u0000\u0004\u00b8\u0001\u0000\u0000\u0000\u0006\u00d7\u0001\u0000"+ - "\u0000\u0000\b\u00d9\u0001\u0000\u0000\u0000\n\u00dc\u0001\u0000\u0000"+ - "\u0000\f\u00de\u0001\u0000\u0000\u0000\u000e\u00e1\u0001\u0000\u0000\u0000"+ - "\u0010\u00ec\u0001\u0000\u0000\u0000\u0012\u00f0\u0001\u0000\u0000\u0000"+ - "\u0014\u00f8\u0001\u0000\u0000\u0000\u0016\u00fd\u0001\u0000\u0000\u0000"+ - "\u0018\u0100\u0001\u0000\u0000\u0000\u001a\u0103\u0001\u0000\u0000\u0000"+ - "\u001c\u0119\u0001\u0000\u0000\u0000\u001e\u011b\u0001\u0000\u0000\u0000"+ - " \u011d\u0001\u0000\u0000\u0000\"\u011f\u0001\u0000\u0000\u0000$\u0121"+ - "\u0001\u0000\u0000\u0000&\u012a\u0001\u0000\u0000\u0000(\u012d\u0001\u0000"+ - "\u0000\u0000*\u0135\u0001\u0000\u0000\u0000,\u013d\u0001\u0000\u0000\u0000"+ - ".\u0142\u0001\u0000\u0000\u00000\u014a\u0001\u0000\u0000\u00002\u0152"+ - "\u0001\u0000\u0000\u00004\u015a\u0001\u0000\u0000\u00006\u015f\u0001\u0000"+ - "\u0000\u00008\u0163\u0001\u0000\u0000\u0000:\u0167\u0001\u0000\u0000\u0000"+ - "<\u016c\u0001\u0000\u0000\u0000>\u016e\u0001\u0000\u0000\u0000@\u0171"+ - "\u0001\u0000\u0000\u0000B\u017a\u0001\u0000\u0000\u0000D\u0182\u0001\u0000"+ - "\u0000\u0000F\u0185\u0001\u0000\u0000\u0000H\u0188\u0001\u0000\u0000\u0000"+ - "J\u0191\u0001\u0000\u0000\u0000L\u0195\u0001\u0000\u0000\u0000N\u019b"+ - "\u0001\u0000\u0000\u0000P\u019f\u0001\u0000\u0000\u0000R\u01a2\u0001\u0000"+ - "\u0000\u0000T\u01aa\u0001\u0000\u0000\u0000V\u01ae\u0001\u0000\u0000\u0000"+ - "X\u01b1\u0001\u0000\u0000\u0000Z\u01b5\u0001\u0000\u0000\u0000\\\u01b8"+ - "\u0001\u0000\u0000\u0000^\u01cc\u0001\u0000\u0000\u0000`\u01d0\u0001\u0000"+ - "\u0000\u0000b\u01d5\u0001\u0000\u0000\u0000d\u01db\u0001\u0000\u0000\u0000"+ - "f\u01e8\u0001\u0000\u0000\u0000h\u01eb\u0001\u0000\u0000\u0000j\u01ef"+ - "\u0001\u0000\u0000\u0000l\u01f3\u0001\u0000\u0000\u0000n\u01f7\u0001\u0000"+ - "\u0000\u0000p\u0208\u0001\u0000\u0000\u0000r\u020a\u0001\u0000\u0000\u0000"+ - "t\u020c\u0001\u0000\u0000\u0000v\u0214\u0001\u0000\u0000\u0000x\u021e"+ - "\u0001\u0000\u0000\u0000z\u023e\u0001\u0000\u0000\u0000|\u0259\u0001\u0000"+ - "\u0000\u0000~\u025b\u0001\u0000\u0000\u0000\u0080\u0268\u0001\u0000\u0000"+ - "\u0000\u0082\u026e\u0001\u0000\u0000\u0000\u0084\u0283\u0001\u0000\u0000"+ - "\u0000\u0086\u028d\u0001\u0000\u0000\u0000\u0088\u02a0\u0001\u0000\u0000"+ - "\u0000\u008a\u02a2\u0001\u0000\u0000\u0000\u008c\u02ad\u0001\u0000\u0000"+ - "\u0000\u008e\u02db\u0001\u0000\u0000\u0000\u0090\u02dd\u0001\u0000\u0000"+ - "\u0000\u0092\u02e1\u0001\u0000\u0000\u0000\u0094\u02e4\u0001\u0000\u0000"+ - "\u0000\u0096\u02e9\u0001\u0000\u0000\u0000\u0098\u02ed\u0001\u0000\u0000"+ - "\u0000\u009a\u02ef\u0001\u0000\u0000\u0000\u009c\u02f1\u0001\u0000\u0000"+ - "\u0000\u009e\u02f6\u0001\u0000\u0000\u0000\u00a0\u02f8\u0001\u0000\u0000"+ - "\u0000\u00a2\u0301\u0001\u0000\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001"+ - "\u0000\u00a5\u00a6\u0005\u0000\u0000\u0001\u00a6\u0001\u0001\u0000\u0000"+ - "\u0000\u00a7\u00a8\u0006\u0001\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004"+ - "\u0002\u0000\u00a9\u00af\u0001\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000"+ - "\u0000\u00ab\u00ac\u00054\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000"+ - "\u00ad\u00aa\u0001\u0000\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000"+ - "\u00af\u00ad\u0001\u0000\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000"+ - "\u00b0\u0003\u0001\u0000\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000"+ - "\u00b2\u00b9\u0003V+\u0000\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9"+ - "\u0003\f\u0006\u0000\u00b5\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002"+ - "\u0001\u0000\u00b7\u00b9\u0003\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b3\u0001\u0000\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000"+ - "\u0000\u00b8\u00b5\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000"+ - "\u0000\u00b9\u0005\u0001\u0000\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000"+ - "\u00bb\u00d8\u0003\b\u0004\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8"+ - "\u0003>\u001f\u0000\u00be\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@"+ - " \u0000\u00c0\u00d8\u0003F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8"+ - "\u0003L&\u0000\u00c3\u00d8\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5"+ - "\u00d8\u0003P(\u0000\u00c6\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003"+ - "d2\u0000\u00c8\u00d8\u0003v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000"+ - "\u00ca\u00d8\u0003b1\u0000\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8"+ - "\u0003`0\u0000\u00cd\u00ce\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f"+ - "3\u0000\u00cf\u00d0\u0004\u0003\u0005\u0000\u00d0\u00d8\u0003h4\u0000"+ - "\u00d1\u00d2\u0004\u0003\u0006\u0000\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4"+ - "\u0004\u0003\u0007\u0000\u00d4\u00d8\u0003r9\u0000\u00d5\u00d6\u0004\u0003"+ - "\b\u0000\u00d6\u00d8\u0003x<\u0000\u00d7\u00ba\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bb\u0001\u0000\u0000\u0000\u00d7\u00bc\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bd\u0001\u0000\u0000\u0000\u00d7\u00be\u0001\u0000\u0000\u0000"+ - "\u00d7\u00bf\u0001\u0000\u0000\u0000\u00d7\u00c0\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c1\u0001\u0000\u0000\u0000\u00d7\u00c2\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c3\u0001\u0000\u0000\u0000\u00d7\u00c4\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c5\u0001\u0000\u0000\u0000\u00d7\u00c6\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c7\u0001\u0000\u0000\u0000\u00d7\u00c8\u0001\u0000\u0000\u0000"+ - "\u00d7\u00c9\u0001\u0000\u0000\u0000\u00d7\u00cb\u0001\u0000\u0000\u0000"+ - "\u00d7\u00cd\u0001\u0000\u0000\u0000\u00d7\u00cf\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d1\u0001\u0000\u0000\u0000\u00d7\u00d3\u0001\u0000\u0000\u0000"+ - "\u00d7\u00d5\u0001\u0000\u0000\u0000\u00d8\u0007\u0001\u0000\u0000\u0000"+ - "\u00d9\u00da\u0005\u000f\u0000\u0000\u00da\u00db\u0003z=\u0000\u00db\t"+ - "\u0001\u0000\u0000\u0000\u00dc\u00dd\u00034\u001a\u0000\u00dd\u000b\u0001"+ - "\u0000\u0000\u0000\u00de\u00df\u0005\f\u0000\u0000\u00df\u00e0\u0003\u000e"+ - "\u0007\u0000\u00e0\r\u0001\u0000\u0000\u0000\u00e1\u00e6\u0003\u0010\b"+ - "\u0000\u00e2\u00e3\u0005>\u0000\u0000\u00e3\u00e5\u0003\u0010\b\u0000"+ - "\u00e4\u00e2\u0001\u0000\u0000\u0000\u00e5\u00e8\u0001\u0000\u0000\u0000"+ - "\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e6\u00e7\u0001\u0000\u0000\u0000"+ - "\u00e7\u000f\u0001\u0000\u0000\u0000\u00e8\u00e6\u0001\u0000\u0000\u0000"+ - "\u00e9\u00ea\u0003.\u0017\u0000\u00ea\u00eb\u0005:\u0000\u0000\u00eb\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ec\u00e9\u0001\u0000\u0000\u0000\u00ec\u00ed"+ - "\u0001\u0000\u0000\u0000\u00ed\u00ee\u0001\u0000\u0000\u0000\u00ee\u00ef"+ - "\u0003z=\u0000\u00ef\u0011\u0001\u0000\u0000\u0000\u00f0\u00f5\u0003\u0014"+ - "\n\u0000\u00f1\u00f2\u0005>\u0000\u0000\u00f2\u00f4\u0003\u0014\n\u0000"+ - "\u00f3\u00f1\u0001\u0000\u0000\u0000\u00f4\u00f7\u0001\u0000\u0000\u0000"+ - "\u00f5\u00f3\u0001\u0000\u0000\u0000\u00f5\u00f6\u0001\u0000\u0000\u0000"+ - "\u00f6\u0013\u0001\u0000\u0000\u0000\u00f7\u00f5\u0001\u0000\u0000\u0000"+ - "\u00f8\u00fb\u0003.\u0017\u0000\u00f9\u00fa\u0005:\u0000\u0000\u00fa\u00fc"+ - "\u0003z=\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000"+ - "\u0000\u0000\u00fc\u0015\u0001\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013"+ - "\u0000\u0000\u00fe\u00ff\u0003\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000"+ - "\u0000\u0100\u0101\u0005\u0014\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000"+ - "\u0102\u0019\u0001\u0000\u0000\u0000\u0103\u0108\u0003\u001c\u000e\u0000"+ - "\u0104\u0105\u0005>\u0000\u0000\u0105\u0107\u0003\u001c\u000e\u0000\u0106"+ - "\u0104\u0001\u0000\u0000\u0000\u0107\u010a\u0001\u0000\u0000\u0000\u0108"+ - "\u0106\u0001\u0000\u0000\u0000\u0108\u0109\u0001\u0000\u0000\u0000\u0109"+ - "\u010c\u0001\u0000\u0000\u0000\u010a\u0108\u0001\u0000\u0000\u0000\u010b"+ - "\u010d\u0003$\u0012\u0000\u010c\u010b\u0001\u0000\u0000\u0000\u010c\u010d"+ - "\u0001\u0000\u0000\u0000\u010d\u001b\u0001\u0000\u0000\u0000\u010e\u010f"+ - "\u0003\u001e\u000f\u0000\u010f\u0110\u0005=\u0000\u0000\u0110\u0112\u0001"+ - "\u0000\u0000\u0000\u0111\u010e\u0001\u0000\u0000\u0000\u0111\u0112\u0001"+ - "\u0000\u0000\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u011a\u0003"+ - "\"\u0011\u0000\u0114\u0117\u0003\"\u0011\u0000\u0115\u0116\u0005<\u0000"+ - "\u0000\u0116\u0118\u0003 \u0010\u0000\u0117\u0115\u0001\u0000\u0000\u0000"+ - "\u0117\u0118\u0001\u0000\u0000\u0000\u0118\u011a\u0001\u0000\u0000\u0000"+ - "\u0119\u0111\u0001\u0000\u0000\u0000\u0119\u0114\u0001\u0000\u0000\u0000"+ - "\u011a\u001d\u0001\u0000\u0000\u0000\u011b\u011c\u0007\u0000\u0000\u0000"+ - "\u011c\u001f\u0001\u0000\u0000\u0000\u011d\u011e\u0007\u0000\u0000\u0000"+ - "\u011e!\u0001\u0000\u0000\u0000\u011f\u0120\u0007\u0000\u0000\u0000\u0120"+ - "#\u0001\u0000\u0000\u0000\u0121\u0122\u0005j\u0000\u0000\u0122\u0127\u0005"+ - "k\u0000\u0000\u0123\u0124\u0005>\u0000\u0000\u0124\u0126\u0005k\u0000"+ - "\u0000\u0125\u0123\u0001\u0000\u0000\u0000\u0126\u0129\u0001\u0000\u0000"+ - "\u0000\u0127\u0125\u0001\u0000\u0000\u0000\u0127\u0128\u0001\u0000\u0000"+ - "\u0000\u0128%\u0001\u0000\u0000\u0000\u0129\u0127\u0001\u0000\u0000\u0000"+ - "\u012a\u012b\u0005\t\u0000\u0000\u012b\u012c\u0003\u000e\u0007\u0000\u012c"+ - "\'\u0001\u0000\u0000\u0000\u012d\u012f\u0005\u000e\u0000\u0000\u012e\u0130"+ - "\u0003*\u0015\u0000\u012f\u012e\u0001\u0000\u0000\u0000\u012f\u0130\u0001"+ - "\u0000\u0000\u0000\u0130\u0133\u0001\u0000\u0000\u0000\u0131\u0132\u0005"+ - ";\u0000\u0000\u0132\u0134\u0003\u000e\u0007\u0000\u0133\u0131\u0001\u0000"+ - "\u0000\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134)\u0001\u0000\u0000"+ - "\u0000\u0135\u013a\u0003,\u0016\u0000\u0136\u0137\u0005>\u0000\u0000\u0137"+ - "\u0139\u0003,\u0016\u0000\u0138\u0136\u0001\u0000\u0000\u0000\u0139\u013c"+ - "\u0001\u0000\u0000\u0000\u013a\u0138\u0001\u0000\u0000\u0000\u013a\u013b"+ - "\u0001\u0000\u0000\u0000\u013b+\u0001\u0000\u0000\u0000\u013c\u013a\u0001"+ - "\u0000\u0000\u0000\u013d\u0140\u0003\u0010\b\u0000\u013e\u013f\u0005\u000f"+ - "\u0000\u0000\u013f\u0141\u0003z=\u0000\u0140\u013e\u0001\u0000\u0000\u0000"+ - "\u0140\u0141\u0001\u0000\u0000\u0000\u0141-\u0001\u0000\u0000\u0000\u0142"+ - "\u0147\u0003<\u001e\u0000\u0143\u0144\u0005@\u0000\u0000\u0144\u0146\u0003"+ - "<\u001e\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0146\u0149\u0001\u0000"+ - "\u0000\u0000\u0147\u0145\u0001\u0000\u0000\u0000\u0147\u0148\u0001\u0000"+ - "\u0000\u0000\u0148/\u0001\u0000\u0000\u0000\u0149\u0147\u0001\u0000\u0000"+ - "\u0000\u014a\u014f\u00036\u001b\u0000\u014b\u014c\u0005@\u0000\u0000\u014c"+ - "\u014e\u00036\u001b\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\u01501\u0001\u0000\u0000\u0000\u0151\u014f\u0001"+ - "\u0000\u0000\u0000\u0152\u0157\u00030\u0018\u0000\u0153\u0154\u0005>\u0000"+ - "\u0000\u0154\u0156\u00030\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000"+ - "\u0156\u0159\u0001\u0000\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000"+ - "\u0157\u0158\u0001\u0000\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159"+ - "\u0157\u0001\u0000\u0000\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b"+ - "5\u0001\u0000\u0000\u0000\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160"+ - "\u00038\u001c\u0000\u015e\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000"+ - "\u0000\u0000\u015f\u015d\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000"+ - "\u0000\u0000\u01607\u0001\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000"+ - "\u0162\u0164\u0005_\u0000\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163"+ - "\u0162\u0001\u0000\u0000\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168"+ - "\u0005^\u0000\u0000\u0166\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000"+ - "\u0000\u0000\u0167\u0166\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000"+ - "\u0000\u0169\u016d\u00034\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b"+ - "\u016d\u0003:\u001d\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a"+ - "\u0001\u0000\u0000\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001"+ - "\u0000\u0000\u0000\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003"+ - "\u008eG\u0000\u0170?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000"+ - "\u0000\u0172\u0177\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174"+ - "\u0176\u0003B!\u0000\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001"+ - "\u0000\u0000\u0000\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001"+ - "\u0000\u0000\u0000\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000"+ - "\u0000\u0000\u017a\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000"+ - "\u017c\u017b\u0001\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000"+ - "\u017d\u0180\u0001\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f"+ - "\u0181\u0007\u0003\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180"+ - "\u0181\u0001\u0000\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183"+ - "\u0005\u001d\u0000\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000"+ - "\u0000\u0000\u0185\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019"+ - "\u0000\u0187G\u0001\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189"+ - "\u018e\u0003J%\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003"+ - "J%\u0000\u018c\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000"+ - "\u0000\u018e\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000"+ - "\u0000\u018fI\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000"+ - "\u0191\u0192\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193"+ - "\u0194\u00030\u0018\u0000\u0194K\u0001\u0000\u0000\u0000\u0195\u0196\u0005"+ - "\b\u0000\u0000\u0196\u0197\u0003\u0084B\u0000\u0197\u0199\u0003\u0098"+ - "L\u0000\u0198\u019a\u0003R)\u0000\u0199\u0198\u0001\u0000\u0000\u0000"+ - "\u0199\u019a\u0001\u0000\u0000\u0000\u019aM\u0001\u0000\u0000\u0000\u019b"+ - "\u019c\u0005\n\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019e"+ - "\u0003\u0098L\u0000\u019eO\u0001\u0000\u0000\u0000\u019f\u01a0\u0005\u001b"+ - "\u0000\u0000\u01a0\u01a1\u0003.\u0017\u0000\u01a1Q\u0001\u0000\u0000\u0000"+ - "\u01a2\u01a7\u0003T*\u0000\u01a3\u01a4\u0005>\u0000\u0000\u01a4\u01a6"+ - "\u0003T*\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\u01a8S\u0001\u0000\u0000\u0000\u01a9\u01a7\u0001\u0000\u0000"+ - "\u0000\u01aa\u01ab\u00034\u001a\u0000\u01ab\u01ac\u0005:\u0000\u0000\u01ac"+ - "\u01ad\u0003\u008eG\u0000\u01adU\u0001\u0000\u0000\u0000\u01ae\u01af\u0005"+ - "\u0006\u0000\u0000\u01af\u01b0\u0003X,\u0000\u01b0W\u0001\u0000\u0000"+ - "\u0000\u01b1\u01b2\u0005a\u0000\u0000\u01b2\u01b3\u0003\u0002\u0001\u0000"+ - "\u01b3\u01b4\u0005b\u0000\u0000\u01b4Y\u0001\u0000\u0000\u0000\u01b5\u01b6"+ - "\u0005!\u0000\u0000\u01b6\u01b7\u0005\u0088\u0000\u0000\u01b7[\u0001\u0000"+ - "\u0000\u0000\u01b8\u01b9\u0005\u0005\u0000\u0000\u01b9\u01bc\u0005&\u0000"+ - "\u0000\u01ba\u01bb\u0005J\u0000\u0000\u01bb\u01bd\u00030\u0018\u0000\u01bc"+ - "\u01ba\u0001\u0000\u0000\u0000\u01bc\u01bd\u0001\u0000\u0000\u0000\u01bd"+ - "\u01c7\u0001\u0000\u0000\u0000\u01be\u01bf\u0005O\u0000\u0000\u01bf\u01c4"+ - "\u0003^/\u0000\u01c0\u01c1\u0005>\u0000\u0000\u01c1\u01c3\u0003^/\u0000"+ - "\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c3\u01c6\u0001\u0000\u0000\u0000"+ - "\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c4\u01c5\u0001\u0000\u0000\u0000"+ - "\u01c5\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000"+ - "\u01c7\u01be\u0001\u0000\u0000\u0000\u01c7\u01c8\u0001\u0000\u0000\u0000"+ - "\u01c8]\u0001\u0000\u0000\u0000\u01c9\u01ca\u00030\u0018\u0000\u01ca\u01cb"+ - "\u0005:\u0000\u0000\u01cb\u01cd\u0001\u0000\u0000\u0000\u01cc\u01c9\u0001"+ - "\u0000\u0000\u0000\u01cc\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001"+ - "\u0000\u0000\u0000\u01ce\u01cf\u00030\u0018\u0000\u01cf_\u0001\u0000\u0000"+ - "\u0000\u01d0\u01d1\u0005\u001a\u0000\u0000\u01d1\u01d2\u0003\u001c\u000e"+ - "\u0000\u01d2\u01d3\u0005J\u0000\u0000\u01d3\u01d4\u00032\u0019\u0000\u01d4"+ - "a\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\u0010\u0000\u0000\u01d6\u01d9"+ - "\u0003*\u0015\u0000\u01d7\u01d8\u0005;\u0000\u0000\u01d8\u01da\u0003\u000e"+ - "\u0007\u0000\u01d9\u01d7\u0001\u0000\u0000\u0000\u01d9\u01da\u0001\u0000"+ - "\u0000\u0000\u01dac\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0004\u0000"+ - "\u0000\u01dc\u01df\u0003.\u0017\u0000\u01dd\u01de\u0005J\u0000\u0000\u01de"+ - "\u01e0\u0003.\u0017\u0000\u01df\u01dd\u0001\u0000\u0000\u0000\u01df\u01e0"+ - "\u0001\u0000\u0000\u0000\u01e0\u01e6\u0001\u0000\u0000\u0000\u01e1\u01e2"+ - "\u0005\u0084\u0000\u0000\u01e2\u01e3\u0003.\u0017\u0000\u01e3\u01e4\u0005"+ - ">\u0000\u0000\u01e4\u01e5\u0003.\u0017\u0000\u01e5\u01e7\u0001\u0000\u0000"+ - "\u0000\u01e6\u01e1\u0001\u0000\u0000\u0000\u01e6\u01e7\u0001\u0000\u0000"+ - "\u0000\u01e7e\u0001\u0000\u0000\u0000\u01e8\u01e9\u0005\u001e\u0000\u0000"+ - "\u01e9\u01ea\u00032\u0019\u0000\u01eag\u0001\u0000\u0000\u0000\u01eb\u01ec"+ - "\u0005\u0015\u0000\u0000\u01ec\u01ed\u0003j5\u0000\u01edi\u0001\u0000"+ - "\u0000\u0000\u01ee\u01f0\u0003l6\u0000\u01ef\u01ee\u0001\u0000\u0000\u0000"+ - "\u01f0\u01f1\u0001\u0000\u0000\u0000\u01f1\u01ef\u0001\u0000\u0000\u0000"+ - "\u01f1\u01f2\u0001\u0000\u0000\u0000\u01f2k\u0001\u0000\u0000\u0000\u01f3"+ - "\u01f4\u0005c\u0000\u0000\u01f4\u01f5\u0003n7\u0000\u01f5\u01f6\u0005"+ - "d\u0000\u0000\u01f6m\u0001\u0000\u0000\u0000\u01f7\u01f8\u00067\uffff"+ - "\uffff\u0000\u01f8\u01f9\u0003p8\u0000\u01f9\u01ff\u0001\u0000\u0000\u0000"+ - "\u01fa\u01fb\n\u0001\u0000\u0000\u01fb\u01fc\u00054\u0000\u0000\u01fc"+ - "\u01fe\u0003p8\u0000\u01fd\u01fa\u0001\u0000\u0000\u0000\u01fe\u0201\u0001"+ - "\u0000\u0000\u0000\u01ff\u01fd\u0001\u0000\u0000\u0000\u01ff\u0200\u0001"+ - "\u0000\u0000\u0000\u0200o\u0001\u0000\u0000\u0000\u0201\u01ff\u0001\u0000"+ - "\u0000\u0000\u0202\u0209\u0003&\u0013\u0000\u0203\u0209\u0003\b\u0004"+ - "\u0000\u0204\u0209\u0003>\u001f\u0000\u0205\u0209\u0003(\u0014\u0000\u0206"+ - "\u0209\u0003@ \u0000\u0207\u0209\u0003L&\u0000\u0208\u0202\u0001\u0000"+ - "\u0000\u0000\u0208\u0203\u0001\u0000\u0000\u0000\u0208\u0204\u0001\u0000"+ - "\u0000\u0000\u0208\u0205\u0001\u0000\u0000\u0000\u0208\u0206\u0001\u0000"+ - "\u0000\u0000\u0208\u0207\u0001\u0000\u0000\u0000\u0209q\u0001\u0000\u0000"+ - "\u0000\u020a\u020b\u0005\u001f\u0000\u0000\u020bs\u0001\u0000\u0000\u0000"+ - "\u020c\u020d\u0005\u0011\u0000\u0000\u020d\u020e\u0003\u008eG\u0000\u020e"+ - "\u020f\u0005J\u0000\u0000\u020f\u0212\u0003\u0012\t\u0000\u0210\u0211"+ - "\u0005O\u0000\u0000\u0211\u0213\u0003<\u001e\u0000\u0212\u0210\u0001\u0000"+ - "\u0000\u0000\u0212\u0213\u0001\u0000\u0000\u0000\u0213u\u0001\u0000\u0000"+ - "\u0000\u0214\u0218\u0005\u0007\u0000\u0000\u0215\u0216\u0003.\u0017\u0000"+ - "\u0216\u0217\u0005:\u0000\u0000\u0217\u0219\u0001\u0000\u0000\u0000\u0218"+ - "\u0215\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219"+ - "\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0003\u0084B\u0000\u021b\u021c"+ - "\u0005O\u0000\u0000\u021c\u021d\u0003<\u001e\u0000\u021dw\u0001\u0000"+ - "\u0000\u0000\u021e\u021f\u0005\u0012\u0000\u0000\u021f\u0220\u0003\u0094"+ - "J\u0000\u0220y\u0001\u0000\u0000\u0000\u0221\u0222\u0006=\uffff\uffff"+ - "\u0000\u0222\u0223\u0005G\u0000\u0000\u0223\u023f\u0003z=\b\u0224\u023f"+ - "\u0003\u0080@\u0000\u0225\u023f\u0003|>\u0000\u0226\u0228\u0003\u0080"+ - "@\u0000\u0227\u0229\u0005G\u0000\u0000\u0228\u0227\u0001\u0000\u0000\u0000"+ - "\u0228\u0229\u0001\u0000\u0000\u0000\u0229\u022a\u0001\u0000\u0000\u0000"+ - "\u022a\u022b\u0005C\u0000\u0000\u022b\u022c\u0005c\u0000\u0000\u022c\u0231"+ - "\u0003\u0080@\u0000\u022d\u022e\u0005>\u0000\u0000\u022e\u0230\u0003\u0080"+ - "@\u0000\u022f\u022d\u0001\u0000\u0000\u0000\u0230\u0233\u0001\u0000\u0000"+ - "\u0000\u0231\u022f\u0001\u0000\u0000\u0000\u0231\u0232\u0001\u0000\u0000"+ - "\u0000\u0232\u0234\u0001\u0000\u0000\u0000\u0233\u0231\u0001\u0000\u0000"+ - "\u0000\u0234\u0235\u0005d\u0000\u0000\u0235\u023f\u0001\u0000\u0000\u0000"+ - "\u0236\u0237\u0003\u0080@\u0000\u0237\u0239\u0005D\u0000\u0000\u0238\u023a"+ - "\u0005G\u0000\u0000\u0239\u0238\u0001\u0000\u0000\u0000\u0239\u023a\u0001"+ - "\u0000\u0000\u0000\u023a\u023b\u0001\u0000\u0000\u0000\u023b\u023c\u0005"+ - "H\u0000\u0000\u023c\u023f\u0001\u0000\u0000\u0000\u023d\u023f\u0003~?"+ - "\u0000\u023e\u0221\u0001\u0000\u0000\u0000\u023e\u0224\u0001\u0000\u0000"+ - "\u0000\u023e\u0225\u0001\u0000\u0000\u0000\u023e\u0226\u0001\u0000\u0000"+ - "\u0000\u023e\u0236\u0001\u0000\u0000\u0000\u023e\u023d\u0001\u0000\u0000"+ - "\u0000\u023f\u0248\u0001\u0000\u0000\u0000\u0240\u0241\n\u0005\u0000\u0000"+ - "\u0241\u0242\u00058\u0000\u0000\u0242\u0247\u0003z=\u0006\u0243\u0244"+ - "\n\u0004\u0000\u0000\u0244\u0245\u0005K\u0000\u0000\u0245\u0247\u0003"+ - "z=\u0005\u0246\u0240\u0001\u0000\u0000\u0000\u0246\u0243\u0001\u0000\u0000"+ - "\u0000\u0247\u024a\u0001\u0000\u0000\u0000\u0248\u0246\u0001\u0000\u0000"+ - "\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249{\u0001\u0000\u0000\u0000"+ - "\u024a\u0248\u0001\u0000\u0000\u0000\u024b\u024d\u0003\u0080@\u0000\u024c"+ - "\u024e\u0005G\u0000\u0000\u024d\u024c\u0001\u0000\u0000\u0000\u024d\u024e"+ - "\u0001\u0000\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f\u0250"+ - "\u0005F\u0000\u0000\u0250\u0251\u0003\u0098L\u0000\u0251\u025a\u0001\u0000"+ - "\u0000\u0000\u0252\u0254\u0003\u0080@\u0000\u0253\u0255\u0005G\u0000\u0000"+ - "\u0254\u0253\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000"+ - "\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0005M\u0000\u0000\u0257"+ - "\u0258\u0003\u0098L\u0000\u0258\u025a\u0001\u0000\u0000\u0000\u0259\u024b"+ - "\u0001\u0000\u0000\u0000\u0259\u0252\u0001\u0000\u0000\u0000\u025a}\u0001"+ - "\u0000\u0000\u0000\u025b\u025e\u0003.\u0017\u0000\u025c\u025d\u0005<\u0000"+ - "\u0000\u025d\u025f\u0003\n\u0005\u0000\u025e\u025c\u0001\u0000\u0000\u0000"+ - "\u025e\u025f\u0001\u0000\u0000\u0000\u025f\u0260\u0001\u0000\u0000\u0000"+ - "\u0260\u0261\u0005=\u0000\u0000\u0261\u0262\u0003\u008eG\u0000\u0262\u007f"+ - "\u0001\u0000\u0000\u0000\u0263\u0269\u0003\u0082A\u0000\u0264\u0265\u0003"+ - "\u0082A\u0000\u0265\u0266\u0003\u009aM\u0000\u0266\u0267\u0003\u0082A"+ - "\u0000\u0267\u0269\u0001\u0000\u0000\u0000\u0268\u0263\u0001\u0000\u0000"+ - "\u0000\u0268\u0264\u0001\u0000\u0000\u0000\u0269\u0081\u0001\u0000\u0000"+ - "\u0000\u026a\u026b\u0006A\uffff\uffff\u0000\u026b\u026f\u0003\u0084B\u0000"+ - "\u026c\u026d\u0007\u0004\u0000\u0000\u026d\u026f\u0003\u0082A\u0003\u026e"+ - "\u026a\u0001\u0000\u0000\u0000\u026e\u026c\u0001\u0000\u0000\u0000\u026f"+ - "\u0278\u0001\u0000\u0000\u0000\u0270\u0271\n\u0002\u0000\u0000\u0271\u0272"+ - "\u0007\u0005\u0000\u0000\u0272\u0277\u0003\u0082A\u0003\u0273\u0274\n"+ - "\u0001\u0000\u0000\u0274\u0275\u0007\u0004\u0000\u0000\u0275\u0277\u0003"+ - "\u0082A\u0002\u0276\u0270\u0001\u0000\u0000\u0000\u0276\u0273\u0001\u0000"+ - "\u0000\u0000\u0277\u027a\u0001\u0000\u0000\u0000\u0278\u0276\u0001\u0000"+ - "\u0000\u0000\u0278\u0279\u0001\u0000\u0000\u0000\u0279\u0083\u0001\u0000"+ - "\u0000\u0000\u027a\u0278\u0001\u0000\u0000\u0000\u027b\u027c\u0006B\uffff"+ - "\uffff\u0000\u027c\u0284\u0003\u008eG\u0000\u027d\u0284\u0003.\u0017\u0000"+ - "\u027e\u0284\u0003\u0086C\u0000\u027f\u0280\u0005c\u0000\u0000\u0280\u0281"+ - "\u0003z=\u0000\u0281\u0282\u0005d\u0000\u0000\u0282\u0284\u0001\u0000"+ - "\u0000\u0000\u0283\u027b\u0001\u0000\u0000\u0000\u0283\u027d\u0001\u0000"+ - "\u0000\u0000\u0283\u027e\u0001\u0000\u0000\u0000\u0283\u027f\u0001\u0000"+ - "\u0000\u0000\u0284\u028a\u0001\u0000\u0000\u0000\u0285\u0286\n\u0001\u0000"+ - "\u0000\u0286\u0287\u0005<\u0000\u0000\u0287\u0289\u0003\n\u0005\u0000"+ - "\u0288\u0285\u0001\u0000\u0000\u0000\u0289\u028c\u0001\u0000\u0000\u0000"+ - "\u028a\u0288\u0001\u0000\u0000\u0000\u028a\u028b\u0001\u0000\u0000\u0000"+ - "\u028b\u0085\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000"+ - "\u028d\u028e\u0003\u0088D\u0000\u028e\u029c\u0005c\u0000\u0000\u028f\u029d"+ - "\u0005Y\u0000\u0000\u0290\u0295\u0003z=\u0000\u0291\u0292\u0005>\u0000"+ - "\u0000\u0292\u0294\u0003z=\u0000\u0293\u0291\u0001\u0000\u0000\u0000\u0294"+ - "\u0297\u0001\u0000\u0000\u0000\u0295\u0293\u0001\u0000\u0000\u0000\u0295"+ - "\u0296\u0001\u0000\u0000\u0000\u0296\u029a\u0001\u0000\u0000\u0000\u0297"+ - "\u0295\u0001\u0000\u0000\u0000\u0298\u0299\u0005>\u0000\u0000\u0299\u029b"+ - "\u0003\u008aE\u0000\u029a\u0298\u0001\u0000\u0000\u0000\u029a\u029b\u0001"+ - "\u0000\u0000\u0000\u029b\u029d\u0001\u0000\u0000\u0000\u029c\u028f\u0001"+ - "\u0000\u0000\u0000\u029c\u0290\u0001\u0000\u0000\u0000\u029c\u029d\u0001"+ - "\u0000\u0000\u0000\u029d\u029e\u0001\u0000\u0000\u0000\u029e\u029f\u0005"+ - "d\u0000\u0000\u029f\u0087\u0001\u0000\u0000\u0000\u02a0\u02a1\u0003<\u001e"+ - "\u0000\u02a1\u0089\u0001\u0000\u0000\u0000\u02a2\u02a3\u0005\\\u0000\u0000"+ - "\u02a3\u02a8\u0003\u008cF\u0000\u02a4\u02a5\u0005>\u0000\u0000\u02a5\u02a7"+ - "\u0003\u008cF\u0000\u02a6\u02a4\u0001\u0000\u0000\u0000\u02a7\u02aa\u0001"+ - "\u0000\u0000\u0000\u02a8\u02a6\u0001\u0000\u0000\u0000\u02a8\u02a9\u0001"+ - "\u0000\u0000\u0000\u02a9\u02ab\u0001\u0000\u0000\u0000\u02aa\u02a8\u0001"+ - "\u0000\u0000\u0000\u02ab\u02ac\u0005]\u0000\u0000\u02ac\u008b\u0001\u0000"+ - "\u0000\u0000\u02ad\u02ae\u0003\u0098L\u0000\u02ae\u02af\u0005=\u0000\u0000"+ - "\u02af\u02b0\u0003\u008eG\u0000\u02b0\u008d\u0001\u0000\u0000\u0000\u02b1"+ - "\u02dc\u0005H\u0000\u0000\u02b2\u02b3\u0003\u0096K\u0000\u02b3\u02b4\u0005"+ - "e\u0000\u0000\u02b4\u02dc\u0001\u0000\u0000\u0000\u02b5\u02dc\u0003\u0094"+ - "J\u0000\u02b6\u02dc\u0003\u0096K\u0000\u02b7\u02dc\u0003\u0090H\u0000"+ - "\u02b8\u02dc\u00038\u001c\u0000\u02b9\u02dc\u0003\u0098L\u0000\u02ba\u02bb"+ - "\u0005a\u0000\u0000\u02bb\u02c0\u0003\u0092I\u0000\u02bc\u02bd\u0005>"+ - "\u0000\u0000\u02bd\u02bf\u0003\u0092I\u0000\u02be\u02bc\u0001\u0000\u0000"+ - "\u0000\u02bf\u02c2\u0001\u0000\u0000\u0000\u02c0\u02be\u0001\u0000\u0000"+ - "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c3\u0001\u0000\u0000"+ - "\u0000\u02c2\u02c0\u0001\u0000\u0000\u0000\u02c3\u02c4\u0005b\u0000\u0000"+ - "\u02c4\u02dc\u0001\u0000\u0000\u0000\u02c5\u02c6\u0005a\u0000\u0000\u02c6"+ - "\u02cb\u0003\u0090H\u0000\u02c7\u02c8\u0005>\u0000\u0000\u02c8\u02ca\u0003"+ - "\u0090H\u0000\u02c9\u02c7\u0001\u0000\u0000\u0000\u02ca\u02cd\u0001\u0000"+ - "\u0000\u0000\u02cb\u02c9\u0001\u0000\u0000\u0000\u02cb\u02cc\u0001\u0000"+ - "\u0000\u0000\u02cc\u02ce\u0001\u0000\u0000\u0000\u02cd\u02cb\u0001\u0000"+ - "\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf\u02dc\u0001\u0000\u0000"+ - "\u0000\u02d0\u02d1\u0005a\u0000\u0000\u02d1\u02d6\u0003\u0098L\u0000\u02d2"+ - "\u02d3\u0005>\u0000\u0000\u02d3\u02d5\u0003\u0098L\u0000\u02d4\u02d2\u0001"+ - "\u0000\u0000\u0000\u02d5\u02d8\u0001\u0000\u0000\u0000\u02d6\u02d4\u0001"+ - "\u0000\u0000\u0000\u02d6\u02d7\u0001\u0000\u0000\u0000\u02d7\u02d9\u0001"+ - "\u0000\u0000\u0000\u02d8\u02d6\u0001\u0000\u0000\u0000\u02d9\u02da\u0005"+ - "b\u0000\u0000\u02da\u02dc\u0001\u0000\u0000\u0000\u02db\u02b1\u0001\u0000"+ - "\u0000\u0000\u02db\u02b2\u0001\u0000\u0000\u0000\u02db\u02b5\u0001\u0000"+ - "\u0000\u0000\u02db\u02b6\u0001\u0000\u0000\u0000\u02db\u02b7\u0001\u0000"+ - "\u0000\u0000\u02db\u02b8\u0001\u0000\u0000\u0000\u02db\u02b9\u0001\u0000"+ - "\u0000\u0000\u02db\u02ba\u0001\u0000\u0000\u0000\u02db\u02c5\u0001\u0000"+ - "\u0000\u0000\u02db\u02d0\u0001\u0000\u0000\u0000\u02dc\u008f\u0001\u0000"+ - "\u0000\u0000\u02dd\u02de\u0007\u0006\u0000\u0000\u02de\u0091\u0001\u0000"+ - "\u0000\u0000\u02df\u02e2\u0003\u0094J\u0000\u02e0\u02e2\u0003\u0096K\u0000"+ - "\u02e1\u02df\u0001\u0000\u0000\u0000\u02e1\u02e0\u0001\u0000\u0000\u0000"+ - "\u02e2\u0093\u0001\u0000\u0000\u0000\u02e3\u02e5\u0007\u0004\u0000\u0000"+ - "\u02e4\u02e3\u0001\u0000\u0000\u0000\u02e4\u02e5\u0001\u0000\u0000\u0000"+ - "\u02e5\u02e6\u0001\u0000\u0000\u0000\u02e6\u02e7\u00057\u0000\u0000\u02e7"+ - "\u0095\u0001\u0000\u0000\u0000\u02e8\u02ea\u0007\u0004\u0000\u0000\u02e9"+ - "\u02e8\u0001\u0000\u0000\u0000\u02e9\u02ea\u0001\u0000\u0000\u0000\u02ea"+ - "\u02eb\u0001\u0000\u0000\u0000\u02eb\u02ec\u00056\u0000\u0000\u02ec\u0097"+ - "\u0001\u0000\u0000\u0000\u02ed\u02ee\u00055\u0000\u0000\u02ee\u0099\u0001"+ - "\u0000\u0000\u0000\u02ef\u02f0\u0007\u0007\u0000\u0000\u02f0\u009b\u0001"+ - "\u0000\u0000\u0000\u02f1\u02f2\u0007\b\u0000\u0000\u02f2\u02f3\u0005r"+ - "\u0000\u0000\u02f3\u02f4\u0003\u009eO\u0000\u02f4\u02f5\u0003\u00a0P\u0000"+ - "\u02f5\u009d\u0001\u0000\u0000\u0000\u02f6\u02f7\u0003\u001c\u000e\u0000"+ - "\u02f7\u009f\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005J\u0000\u0000\u02f9"+ - "\u02fe\u0003\u00a2Q\u0000\u02fa\u02fb\u0005>\u0000\u0000\u02fb\u02fd\u0003"+ - "\u00a2Q\u0000\u02fc\u02fa\u0001\u0000\u0000\u0000\u02fd\u0300\u0001\u0000"+ - "\u0000\u0000\u02fe\u02fc\u0001\u0000\u0000\u0000\u02fe\u02ff\u0001\u0000"+ - "\u0000\u0000\u02ff\u00a1\u0001\u0000\u0000\u0000\u0300\u02fe\u0001\u0000"+ - "\u0000\u0000\u0301\u0302\u0003\u0080@\u0000\u0302\u00a3\u0001\u0000\u0000"+ - "\u0000F\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ + "%\u0001%\u0001%\u0001%\u0003%\u019a\b%\u0001&\u0001&\u0001&\u0001&\u0003"+ + "&\u01a0\b&\u0001\'\u0001\'\u0001\'\u0001\'\u0001(\u0001(\u0001(\u0001"+ + ")\u0001)\u0001)\u0005)\u01ac\b)\n)\f)\u01af\t)\u0001*\u0001*\u0001*\u0001"+ + "*\u0001+\u0001+\u0001+\u0001,\u0001,\u0001,\u0001,\u0001-\u0001-\u0001"+ + "-\u0001.\u0001.\u0001.\u0001.\u0003.\u01c3\b.\u0001.\u0001.\u0001.\u0001"+ + ".\u0005.\u01c9\b.\n.\f.\u01cc\t.\u0003.\u01ce\b.\u0001/\u0001/\u0001/"+ + "\u0003/\u01d3\b/\u0001/\u0001/\u00010\u00010\u00010\u00010\u00010\u0001"+ + "1\u00011\u00011\u00011\u00031\u01e0\b1\u00012\u00012\u00012\u00012\u0003"+ + "2\u01e6\b2\u00012\u00012\u00012\u00012\u00012\u00032\u01ed\b2\u00013\u0001"+ + "3\u00013\u00014\u00014\u00014\u00015\u00045\u01f6\b5\u000b5\f5\u01f7\u0001"+ + "6\u00016\u00016\u00016\u00017\u00017\u00017\u00017\u00017\u00017\u0005"+ + "7\u0204\b7\n7\f7\u0207\t7\u00018\u00018\u00018\u00018\u00018\u00018\u0003"+ + "8\u020f\b8\u00019\u00019\u0001:\u0001:\u0001:\u0001:\u0001:\u0001:\u0003"+ + ":\u0219\b:\u0001;\u0001;\u0001;\u0001;\u0003;\u021f\b;\u0001;\u0001;\u0001"+ + ";\u0001;\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0003=\u022f\b=\u0001=\u0001=\u0001=\u0001=\u0001=\u0005=\u0236"+ + "\b=\n=\f=\u0239\t=\u0001=\u0001=\u0001=\u0001=\u0001=\u0003=\u0240\b="+ + "\u0001=\u0001=\u0001=\u0003=\u0245\b=\u0001=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001=\u0005=\u024d\b=\n=\f=\u0250\t=\u0001>\u0001>\u0003>\u0254\b>"+ + "\u0001>\u0001>\u0001>\u0001>\u0001>\u0003>\u025b\b>\u0001>\u0001>\u0001"+ + ">\u0003>\u0260\b>\u0001?\u0001?\u0001?\u0003?\u0265\b?\u0001?\u0001?\u0001"+ + "?\u0001@\u0001@\u0001@\u0001@\u0001@\u0003@\u026f\b@\u0001A\u0001A\u0001"+ + "A\u0001A\u0003A\u0275\bA\u0001A\u0001A\u0001A\u0001A\u0001A\u0001A\u0005"+ + "A\u027d\bA\nA\fA\u0280\tA\u0001B\u0001B\u0001B\u0001B\u0001B\u0001B\u0001"+ + "B\u0001B\u0003B\u028a\bB\u0001B\u0001B\u0001B\u0005B\u028f\bB\nB\fB\u0292"+ + "\tB\u0001C\u0001C\u0001C\u0001C\u0001C\u0001C\u0005C\u029a\bC\nC\fC\u029d"+ + "\tC\u0001C\u0001C\u0003C\u02a1\bC\u0003C\u02a3\bC\u0001C\u0001C\u0001"+ + "D\u0001D\u0001E\u0001E\u0001E\u0001E\u0005E\u02ad\bE\nE\fE\u02b0\tE\u0001"+ + "E\u0001E\u0001F\u0001F\u0001F\u0001F\u0001G\u0001G\u0001G\u0001G\u0001"+ + "G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02c5"+ + "\bG\nG\fG\u02c8\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02d0"+ + "\bG\nG\fG\u02d3\tG\u0001G\u0001G\u0001G\u0001G\u0001G\u0001G\u0005G\u02db"+ + "\bG\nG\fG\u02de\tG\u0001G\u0001G\u0003G\u02e2\bG\u0001H\u0001H\u0001I"+ + "\u0001I\u0003I\u02e8\bI\u0001J\u0003J\u02eb\bJ\u0001J\u0001J\u0001K\u0003"+ + "K\u02f0\bK\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001"+ + "N\u0001N\u0001N\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0005P\u0303"+ + "\bP\nP\fP\u0306\tP\u0001Q\u0001Q\u0001Q\u0000\u0005\u0002nz\u0082\u0084"+ + "R\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ + "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjlnprtvxz|~\u0080\u0082"+ + "\u0084\u0086\u0088\u008a\u008c\u008e\u0090\u0092\u0094\u0096\u0098\u009a"+ + "\u009c\u009e\u00a0\u00a2\u0000\t\u0002\u000055kk\u0001\u0000ef\u0002\u0000"+ + "99??\u0002\u0000BBEE\u0001\u0000WX\u0001\u0000Y[\u0002\u0000AANN\u0002"+ + "\u0000PPRV\u0002\u0000\u0016\u0016\u0018\u0019\u032a\u0000\u00a4\u0001"+ + "\u0000\u0000\u0000\u0002\u00a7\u0001\u0000\u0000\u0000\u0004\u00b8\u0001"+ + "\u0000\u0000\u0000\u0006\u00d7\u0001\u0000\u0000\u0000\b\u00d9\u0001\u0000"+ + "\u0000\u0000\n\u00dc\u0001\u0000\u0000\u0000\f\u00de\u0001\u0000\u0000"+ + "\u0000\u000e\u00e1\u0001\u0000\u0000\u0000\u0010\u00ec\u0001\u0000\u0000"+ + "\u0000\u0012\u00f0\u0001\u0000\u0000\u0000\u0014\u00f8\u0001\u0000\u0000"+ + "\u0000\u0016\u00fd\u0001\u0000\u0000\u0000\u0018\u0100\u0001\u0000\u0000"+ + "\u0000\u001a\u0103\u0001\u0000\u0000\u0000\u001c\u0119\u0001\u0000\u0000"+ + "\u0000\u001e\u011b\u0001\u0000\u0000\u0000 \u011d\u0001\u0000\u0000\u0000"+ + "\"\u011f\u0001\u0000\u0000\u0000$\u0121\u0001\u0000\u0000\u0000&\u012a"+ + "\u0001\u0000\u0000\u0000(\u012d\u0001\u0000\u0000\u0000*\u0135\u0001\u0000"+ + "\u0000\u0000,\u013d\u0001\u0000\u0000\u0000.\u0142\u0001\u0000\u0000\u0000"+ + "0\u014a\u0001\u0000\u0000\u00002\u0152\u0001\u0000\u0000\u00004\u015a"+ + "\u0001\u0000\u0000\u00006\u015f\u0001\u0000\u0000\u00008\u0163\u0001\u0000"+ + "\u0000\u0000:\u0167\u0001\u0000\u0000\u0000<\u016c\u0001\u0000\u0000\u0000"+ + ">\u016e\u0001\u0000\u0000\u0000@\u0171\u0001\u0000\u0000\u0000B\u017a"+ + "\u0001\u0000\u0000\u0000D\u0182\u0001\u0000\u0000\u0000F\u0185\u0001\u0000"+ + "\u0000\u0000H\u0188\u0001\u0000\u0000\u0000J\u0199\u0001\u0000\u0000\u0000"+ + "L\u019b\u0001\u0000\u0000\u0000N\u01a1\u0001\u0000\u0000\u0000P\u01a5"+ + "\u0001\u0000\u0000\u0000R\u01a8\u0001\u0000\u0000\u0000T\u01b0\u0001\u0000"+ + "\u0000\u0000V\u01b4\u0001\u0000\u0000\u0000X\u01b7\u0001\u0000\u0000\u0000"+ + "Z\u01bb\u0001\u0000\u0000\u0000\\\u01be\u0001\u0000\u0000\u0000^\u01d2"+ + "\u0001\u0000\u0000\u0000`\u01d6\u0001\u0000\u0000\u0000b\u01db\u0001\u0000"+ + "\u0000\u0000d\u01e1\u0001\u0000\u0000\u0000f\u01ee\u0001\u0000\u0000\u0000"+ + "h\u01f1\u0001\u0000\u0000\u0000j\u01f5\u0001\u0000\u0000\u0000l\u01f9"+ + "\u0001\u0000\u0000\u0000n\u01fd\u0001\u0000\u0000\u0000p\u020e\u0001\u0000"+ + "\u0000\u0000r\u0210\u0001\u0000\u0000\u0000t\u0212\u0001\u0000\u0000\u0000"+ + "v\u021a\u0001\u0000\u0000\u0000x\u0224\u0001\u0000\u0000\u0000z\u0244"+ + "\u0001\u0000\u0000\u0000|\u025f\u0001\u0000\u0000\u0000~\u0261\u0001\u0000"+ + "\u0000\u0000\u0080\u026e\u0001\u0000\u0000\u0000\u0082\u0274\u0001\u0000"+ + "\u0000\u0000\u0084\u0289\u0001\u0000\u0000\u0000\u0086\u0293\u0001\u0000"+ + "\u0000\u0000\u0088\u02a6\u0001\u0000\u0000\u0000\u008a\u02a8\u0001\u0000"+ + "\u0000\u0000\u008c\u02b3\u0001\u0000\u0000\u0000\u008e\u02e1\u0001\u0000"+ + "\u0000\u0000\u0090\u02e3\u0001\u0000\u0000\u0000\u0092\u02e7\u0001\u0000"+ + "\u0000\u0000\u0094\u02ea\u0001\u0000\u0000\u0000\u0096\u02ef\u0001\u0000"+ + "\u0000\u0000\u0098\u02f3\u0001\u0000\u0000\u0000\u009a\u02f5\u0001\u0000"+ + "\u0000\u0000\u009c\u02f7\u0001\u0000\u0000\u0000\u009e\u02fc\u0001\u0000"+ + "\u0000\u0000\u00a0\u02fe\u0001\u0000\u0000\u0000\u00a2\u0307\u0001\u0000"+ + "\u0000\u0000\u00a4\u00a5\u0003\u0002\u0001\u0000\u00a5\u00a6\u0005\u0000"+ + "\u0000\u0001\u00a6\u0001\u0001\u0000\u0000\u0000\u00a7\u00a8\u0006\u0001"+ + "\uffff\uffff\u0000\u00a8\u00a9\u0003\u0004\u0002\u0000\u00a9\u00af\u0001"+ + "\u0000\u0000\u0000\u00aa\u00ab\n\u0001\u0000\u0000\u00ab\u00ac\u00054"+ + "\u0000\u0000\u00ac\u00ae\u0003\u0006\u0003\u0000\u00ad\u00aa\u0001\u0000"+ + "\u0000\u0000\u00ae\u00b1\u0001\u0000\u0000\u0000\u00af\u00ad\u0001\u0000"+ + "\u0000\u0000\u00af\u00b0\u0001\u0000\u0000\u0000\u00b0\u0003\u0001\u0000"+ + "\u0000\u0000\u00b1\u00af\u0001\u0000\u0000\u0000\u00b2\u00b9\u0003V+\u0000"+ + "\u00b3\u00b9\u0003\u0016\u000b\u0000\u00b4\u00b9\u0003\f\u0006\u0000\u00b5"+ + "\u00b9\u0003Z-\u0000\u00b6\u00b7\u0004\u0002\u0001\u0000\u00b7\u00b9\u0003"+ + "\u0018\f\u0000\u00b8\u00b2\u0001\u0000\u0000\u0000\u00b8\u00b3\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b4\u0001\u0000\u0000\u0000\u00b8\u00b5\u0001\u0000"+ + "\u0000\u0000\u00b8\u00b6\u0001\u0000\u0000\u0000\u00b9\u0005\u0001\u0000"+ + "\u0000\u0000\u00ba\u00d8\u0003&\u0013\u0000\u00bb\u00d8\u0003\b\u0004"+ + "\u0000\u00bc\u00d8\u0003D\"\u0000\u00bd\u00d8\u0003>\u001f\u0000\u00be"+ + "\u00d8\u0003(\u0014\u0000\u00bf\u00d8\u0003@ \u0000\u00c0\u00d8\u0003"+ + "F#\u0000\u00c1\u00d8\u0003H$\u0000\u00c2\u00d8\u0003L&\u0000\u00c3\u00d8"+ + "\u0003N\'\u0000\u00c4\u00d8\u0003\\.\u0000\u00c5\u00d8\u0003P(\u0000\u00c6"+ + "\u00d8\u0003\u009cN\u0000\u00c7\u00d8\u0003d2\u0000\u00c8\u00d8\u0003"+ + "v;\u0000\u00c9\u00ca\u0004\u0003\u0002\u0000\u00ca\u00d8\u0003b1\u0000"+ + "\u00cb\u00cc\u0004\u0003\u0003\u0000\u00cc\u00d8\u0003`0\u0000\u00cd\u00ce"+ + "\u0004\u0003\u0004\u0000\u00ce\u00d8\u0003f3\u0000\u00cf\u00d0\u0004\u0003"+ + "\u0005\u0000\u00d0\u00d8\u0003h4\u0000\u00d1\u00d2\u0004\u0003\u0006\u0000"+ + "\u00d2\u00d8\u0003t:\u0000\u00d3\u00d4\u0004\u0003\u0007\u0000\u00d4\u00d8"+ + "\u0003r9\u0000\u00d5\u00d6\u0004\u0003\b\u0000\u00d6\u00d8\u0003x<\u0000"+ + "\u00d7\u00ba\u0001\u0000\u0000\u0000\u00d7\u00bb\u0001\u0000\u0000\u0000"+ + "\u00d7\u00bc\u0001\u0000\u0000\u0000\u00d7\u00bd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00be\u0001\u0000\u0000\u0000\u00d7\u00bf\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c0\u0001\u0000\u0000\u0000\u00d7\u00c1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c2\u0001\u0000\u0000\u0000\u00d7\u00c3\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c4\u0001\u0000\u0000\u0000\u00d7\u00c5\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c6\u0001\u0000\u0000\u0000\u00d7\u00c7\u0001\u0000\u0000\u0000"+ + "\u00d7\u00c8\u0001\u0000\u0000\u0000\u00d7\u00c9\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cb\u0001\u0000\u0000\u0000\u00d7\u00cd\u0001\u0000\u0000\u0000"+ + "\u00d7\u00cf\u0001\u0000\u0000\u0000\u00d7\u00d1\u0001\u0000\u0000\u0000"+ + "\u00d7\u00d3\u0001\u0000\u0000\u0000\u00d7\u00d5\u0001\u0000\u0000\u0000"+ + "\u00d8\u0007\u0001\u0000\u0000\u0000\u00d9\u00da\u0005\u000f\u0000\u0000"+ + "\u00da\u00db\u0003z=\u0000\u00db\t\u0001\u0000\u0000\u0000\u00dc\u00dd"+ + "\u00034\u001a\u0000\u00dd\u000b\u0001\u0000\u0000\u0000\u00de\u00df\u0005"+ + "\f\u0000\u0000\u00df\u00e0\u0003\u000e\u0007\u0000\u00e0\r\u0001\u0000"+ + "\u0000\u0000\u00e1\u00e6\u0003\u0010\b\u0000\u00e2\u00e3\u0005>\u0000"+ + "\u0000\u00e3\u00e5\u0003\u0010\b\u0000\u00e4\u00e2\u0001\u0000\u0000\u0000"+ + "\u00e5\u00e8\u0001\u0000\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000"+ + "\u00e6\u00e7\u0001\u0000\u0000\u0000\u00e7\u000f\u0001\u0000\u0000\u0000"+ + "\u00e8\u00e6\u0001\u0000\u0000\u0000\u00e9\u00ea\u0003.\u0017\u0000\u00ea"+ + "\u00eb\u0005:\u0000\u0000\u00eb\u00ed\u0001\u0000\u0000\u0000\u00ec\u00e9"+ + "\u0001\u0000\u0000\u0000\u00ec\u00ed\u0001\u0000\u0000\u0000\u00ed\u00ee"+ + "\u0001\u0000\u0000\u0000\u00ee\u00ef\u0003z=\u0000\u00ef\u0011\u0001\u0000"+ + "\u0000\u0000\u00f0\u00f5\u0003\u0014\n\u0000\u00f1\u00f2\u0005>\u0000"+ + "\u0000\u00f2\u00f4\u0003\u0014\n\u0000\u00f3\u00f1\u0001\u0000\u0000\u0000"+ + "\u00f4\u00f7\u0001\u0000\u0000\u0000\u00f5\u00f3\u0001\u0000\u0000\u0000"+ + "\u00f5\u00f6\u0001\u0000\u0000\u0000\u00f6\u0013\u0001\u0000\u0000\u0000"+ + "\u00f7\u00f5\u0001\u0000\u0000\u0000\u00f8\u00fb\u0003.\u0017\u0000\u00f9"+ + "\u00fa\u0005:\u0000\u0000\u00fa\u00fc\u0003z=\u0000\u00fb\u00f9\u0001"+ + "\u0000\u0000\u0000\u00fb\u00fc\u0001\u0000\u0000\u0000\u00fc\u0015\u0001"+ + "\u0000\u0000\u0000\u00fd\u00fe\u0005\u0013\u0000\u0000\u00fe\u00ff\u0003"+ + "\u001a\r\u0000\u00ff\u0017\u0001\u0000\u0000\u0000\u0100\u0101\u0005\u0014"+ + "\u0000\u0000\u0101\u0102\u0003\u001a\r\u0000\u0102\u0019\u0001\u0000\u0000"+ + "\u0000\u0103\u0108\u0003\u001c\u000e\u0000\u0104\u0105\u0005>\u0000\u0000"+ + "\u0105\u0107\u0003\u001c\u000e\u0000\u0106\u0104\u0001\u0000\u0000\u0000"+ + "\u0107\u010a\u0001\u0000\u0000\u0000\u0108\u0106\u0001\u0000\u0000\u0000"+ + "\u0108\u0109\u0001\u0000\u0000\u0000\u0109\u010c\u0001\u0000\u0000\u0000"+ + "\u010a\u0108\u0001\u0000\u0000\u0000\u010b\u010d\u0003$\u0012\u0000\u010c"+ + "\u010b\u0001\u0000\u0000\u0000\u010c\u010d\u0001\u0000\u0000\u0000\u010d"+ + "\u001b\u0001\u0000\u0000\u0000\u010e\u010f\u0003\u001e\u000f\u0000\u010f"+ + "\u0110\u0005=\u0000\u0000\u0110\u0112\u0001\u0000\u0000\u0000\u0111\u010e"+ + "\u0001\u0000\u0000\u0000\u0111\u0112\u0001\u0000\u0000\u0000\u0112\u0113"+ + "\u0001\u0000\u0000\u0000\u0113\u011a\u0003\"\u0011\u0000\u0114\u0117\u0003"+ + "\"\u0011\u0000\u0115\u0116\u0005<\u0000\u0000\u0116\u0118\u0003 \u0010"+ + "\u0000\u0117\u0115\u0001\u0000\u0000\u0000\u0117\u0118\u0001\u0000\u0000"+ + "\u0000\u0118\u011a\u0001\u0000\u0000\u0000\u0119\u0111\u0001\u0000\u0000"+ + "\u0000\u0119\u0114\u0001\u0000\u0000\u0000\u011a\u001d\u0001\u0000\u0000"+ + "\u0000\u011b\u011c\u0007\u0000\u0000\u0000\u011c\u001f\u0001\u0000\u0000"+ + "\u0000\u011d\u011e\u0007\u0000\u0000\u0000\u011e!\u0001\u0000\u0000\u0000"+ + "\u011f\u0120\u0007\u0000\u0000\u0000\u0120#\u0001\u0000\u0000\u0000\u0121"+ + "\u0122\u0005j\u0000\u0000\u0122\u0127\u0005k\u0000\u0000\u0123\u0124\u0005"+ + ">\u0000\u0000\u0124\u0126\u0005k\u0000\u0000\u0125\u0123\u0001\u0000\u0000"+ + "\u0000\u0126\u0129\u0001\u0000\u0000\u0000\u0127\u0125\u0001\u0000\u0000"+ + "\u0000\u0127\u0128\u0001\u0000\u0000\u0000\u0128%\u0001\u0000\u0000\u0000"+ + "\u0129\u0127\u0001\u0000\u0000\u0000\u012a\u012b\u0005\t\u0000\u0000\u012b"+ + "\u012c\u0003\u000e\u0007\u0000\u012c\'\u0001\u0000\u0000\u0000\u012d\u012f"+ + "\u0005\u000e\u0000\u0000\u012e\u0130\u0003*\u0015\u0000\u012f\u012e\u0001"+ + "\u0000\u0000\u0000\u012f\u0130\u0001\u0000\u0000\u0000\u0130\u0133\u0001"+ + "\u0000\u0000\u0000\u0131\u0132\u0005;\u0000\u0000\u0132\u0134\u0003\u000e"+ + "\u0007\u0000\u0133\u0131\u0001\u0000\u0000\u0000\u0133\u0134\u0001\u0000"+ + "\u0000\u0000\u0134)\u0001\u0000\u0000\u0000\u0135\u013a\u0003,\u0016\u0000"+ + "\u0136\u0137\u0005>\u0000\u0000\u0137\u0139\u0003,\u0016\u0000\u0138\u0136"+ + "\u0001\u0000\u0000\u0000\u0139\u013c\u0001\u0000\u0000\u0000\u013a\u0138"+ + "\u0001\u0000\u0000\u0000\u013a\u013b\u0001\u0000\u0000\u0000\u013b+\u0001"+ + "\u0000\u0000\u0000\u013c\u013a\u0001\u0000\u0000\u0000\u013d\u0140\u0003"+ + "\u0010\b\u0000\u013e\u013f\u0005\u000f\u0000\u0000\u013f\u0141\u0003z"+ + "=\u0000\u0140\u013e\u0001\u0000\u0000\u0000\u0140\u0141\u0001\u0000\u0000"+ + "\u0000\u0141-\u0001\u0000\u0000\u0000\u0142\u0147\u0003<\u001e\u0000\u0143"+ + "\u0144\u0005@\u0000\u0000\u0144\u0146\u0003<\u001e\u0000\u0145\u0143\u0001"+ + "\u0000\u0000\u0000\u0146\u0149\u0001\u0000\u0000\u0000\u0147\u0145\u0001"+ + "\u0000\u0000\u0000\u0147\u0148\u0001\u0000\u0000\u0000\u0148/\u0001\u0000"+ + "\u0000\u0000\u0149\u0147\u0001\u0000\u0000\u0000\u014a\u014f\u00036\u001b"+ + "\u0000\u014b\u014c\u0005@\u0000\u0000\u014c\u014e\u00036\u001b\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"+ + "1\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000\u0152\u0157"+ + "\u00030\u0018\u0000\u0153\u0154\u0005>\u0000\u0000\u0154\u0156\u00030"+ + "\u0018\u0000\u0155\u0153\u0001\u0000\u0000\u0000\u0156\u0159\u0001\u0000"+ + "\u0000\u0000\u0157\u0155\u0001\u0000\u0000\u0000\u0157\u0158\u0001\u0000"+ + "\u0000\u0000\u01583\u0001\u0000\u0000\u0000\u0159\u0157\u0001\u0000\u0000"+ + "\u0000\u015a\u015b\u0007\u0001\u0000\u0000\u015b5\u0001\u0000\u0000\u0000"+ + "\u015c\u0160\u0005\u0080\u0000\u0000\u015d\u0160\u00038\u001c\u0000\u015e"+ + "\u0160\u0003:\u001d\u0000\u015f\u015c\u0001\u0000\u0000\u0000\u015f\u015d"+ + "\u0001\u0000\u0000\u0000\u015f\u015e\u0001\u0000\u0000\u0000\u01607\u0001"+ + "\u0000\u0000\u0000\u0161\u0164\u0005L\u0000\u0000\u0162\u0164\u0005_\u0000"+ + "\u0000\u0163\u0161\u0001\u0000\u0000\u0000\u0163\u0162\u0001\u0000\u0000"+ + "\u0000\u01649\u0001\u0000\u0000\u0000\u0165\u0168\u0005^\u0000\u0000\u0166"+ + "\u0168\u0005`\u0000\u0000\u0167\u0165\u0001\u0000\u0000\u0000\u0167\u0166"+ + "\u0001\u0000\u0000\u0000\u0168;\u0001\u0000\u0000\u0000\u0169\u016d\u0003"+ + "4\u001a\u0000\u016a\u016d\u00038\u001c\u0000\u016b\u016d\u0003:\u001d"+ + "\u0000\u016c\u0169\u0001\u0000\u0000\u0000\u016c\u016a\u0001\u0000\u0000"+ + "\u0000\u016c\u016b\u0001\u0000\u0000\u0000\u016d=\u0001\u0000\u0000\u0000"+ + "\u016e\u016f\u0005\u000b\u0000\u0000\u016f\u0170\u0003\u008eG\u0000\u0170"+ + "?\u0001\u0000\u0000\u0000\u0171\u0172\u0005\r\u0000\u0000\u0172\u0177"+ + "\u0003B!\u0000\u0173\u0174\u0005>\u0000\u0000\u0174\u0176\u0003B!\u0000"+ + "\u0175\u0173\u0001\u0000\u0000\u0000\u0176\u0179\u0001\u0000\u0000\u0000"+ + "\u0177\u0175\u0001\u0000\u0000\u0000\u0177\u0178\u0001\u0000\u0000\u0000"+ + "\u0178A\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000\u017a"+ + "\u017c\u0003z=\u0000\u017b\u017d\u0007\u0002\u0000\u0000\u017c\u017b\u0001"+ + "\u0000\u0000\u0000\u017c\u017d\u0001\u0000\u0000\u0000\u017d\u0180\u0001"+ + "\u0000\u0000\u0000\u017e\u017f\u0005I\u0000\u0000\u017f\u0181\u0007\u0003"+ + "\u0000\u0000\u0180\u017e\u0001\u0000\u0000\u0000\u0180\u0181\u0001\u0000"+ + "\u0000\u0000\u0181C\u0001\u0000\u0000\u0000\u0182\u0183\u0005\u001d\u0000"+ + "\u0000\u0183\u0184\u00032\u0019\u0000\u0184E\u0001\u0000\u0000\u0000\u0185"+ + "\u0186\u0005\u001c\u0000\u0000\u0186\u0187\u00032\u0019\u0000\u0187G\u0001"+ + "\u0000\u0000\u0000\u0188\u0189\u0005 \u0000\u0000\u0189\u018e\u0003J%"+ + "\u0000\u018a\u018b\u0005>\u0000\u0000\u018b\u018d\u0003J%\u0000\u018c"+ + "\u018a\u0001\u0000\u0000\u0000\u018d\u0190\u0001\u0000\u0000\u0000\u018e"+ + "\u018c\u0001\u0000\u0000\u0000\u018e\u018f\u0001\u0000\u0000\u0000\u018f"+ + "I\u0001\u0000\u0000\u0000\u0190\u018e\u0001\u0000\u0000\u0000\u0191\u0192"+ + "\u00030\u0018\u0000\u0192\u0193\u0005\u0084\u0000\u0000\u0193\u0194\u0003"+ + "0\u0018\u0000\u0194\u019a\u0001\u0000\u0000\u0000\u0195\u0196\u00030\u0018"+ + "\u0000\u0196\u0197\u0005:\u0000\u0000\u0197\u0198\u00030\u0018\u0000\u0198"+ + "\u019a\u0001\u0000\u0000\u0000\u0199\u0191\u0001\u0000\u0000\u0000\u0199"+ + "\u0195\u0001\u0000\u0000\u0000\u019aK\u0001\u0000\u0000\u0000\u019b\u019c"+ + "\u0005\b\u0000\u0000\u019c\u019d\u0003\u0084B\u0000\u019d\u019f\u0003"+ + "\u0098L\u0000\u019e\u01a0\u0003R)\u0000\u019f\u019e\u0001\u0000\u0000"+ + "\u0000\u019f\u01a0\u0001\u0000\u0000\u0000\u01a0M\u0001\u0000\u0000\u0000"+ + "\u01a1\u01a2\u0005\n\u0000\u0000\u01a2\u01a3\u0003\u0084B\u0000\u01a3"+ + "\u01a4\u0003\u0098L\u0000\u01a4O\u0001\u0000\u0000\u0000\u01a5\u01a6\u0005"+ + "\u001b\u0000\u0000\u01a6\u01a7\u0003.\u0017\u0000\u01a7Q\u0001\u0000\u0000"+ + "\u0000\u01a8\u01ad\u0003T*\u0000\u01a9\u01aa\u0005>\u0000\u0000\u01aa"+ + "\u01ac\u0003T*\u0000\u01ab\u01a9\u0001\u0000\u0000\u0000\u01ac\u01af\u0001"+ + "\u0000\u0000\u0000\u01ad\u01ab\u0001\u0000\u0000\u0000\u01ad\u01ae\u0001"+ + "\u0000\u0000\u0000\u01aeS\u0001\u0000\u0000\u0000\u01af\u01ad\u0001\u0000"+ + "\u0000\u0000\u01b0\u01b1\u00034\u001a\u0000\u01b1\u01b2\u0005:\u0000\u0000"+ + "\u01b2\u01b3\u0003\u008eG\u0000\u01b3U\u0001\u0000\u0000\u0000\u01b4\u01b5"+ + "\u0005\u0006\u0000\u0000\u01b5\u01b6\u0003X,\u0000\u01b6W\u0001\u0000"+ + "\u0000\u0000\u01b7\u01b8\u0005a\u0000\u0000\u01b8\u01b9\u0003\u0002\u0001"+ + "\u0000\u01b9\u01ba\u0005b\u0000\u0000\u01baY\u0001\u0000\u0000\u0000\u01bb"+ + "\u01bc\u0005!\u0000\u0000\u01bc\u01bd\u0005\u0088\u0000\u0000\u01bd[\u0001"+ + "\u0000\u0000\u0000\u01be\u01bf\u0005\u0005\u0000\u0000\u01bf\u01c2\u0005"+ + "&\u0000\u0000\u01c0\u01c1\u0005J\u0000\u0000\u01c1\u01c3\u00030\u0018"+ + "\u0000\u01c2\u01c0\u0001\u0000\u0000\u0000\u01c2\u01c3\u0001\u0000\u0000"+ + "\u0000\u01c3\u01cd\u0001\u0000\u0000\u0000\u01c4\u01c5\u0005O\u0000\u0000"+ + "\u01c5\u01ca\u0003^/\u0000\u01c6\u01c7\u0005>\u0000\u0000\u01c7\u01c9"+ + "\u0003^/\u0000\u01c8\u01c6\u0001\u0000\u0000\u0000\u01c9\u01cc\u0001\u0000"+ + "\u0000\u0000\u01ca\u01c8\u0001\u0000\u0000\u0000\u01ca\u01cb\u0001\u0000"+ + "\u0000\u0000\u01cb\u01ce\u0001\u0000\u0000\u0000\u01cc\u01ca\u0001\u0000"+ + "\u0000\u0000\u01cd\u01c4\u0001\u0000\u0000\u0000\u01cd\u01ce\u0001\u0000"+ + "\u0000\u0000\u01ce]\u0001\u0000\u0000\u0000\u01cf\u01d0\u00030\u0018\u0000"+ + "\u01d0\u01d1\u0005:\u0000\u0000\u01d1\u01d3\u0001\u0000\u0000\u0000\u01d2"+ + "\u01cf\u0001\u0000\u0000\u0000\u01d2\u01d3\u0001\u0000\u0000\u0000\u01d3"+ + "\u01d4\u0001\u0000\u0000\u0000\u01d4\u01d5\u00030\u0018\u0000\u01d5_\u0001"+ + "\u0000\u0000\u0000\u01d6\u01d7\u0005\u001a\u0000\u0000\u01d7\u01d8\u0003"+ + "\u001c\u000e\u0000\u01d8\u01d9\u0005J\u0000\u0000\u01d9\u01da\u00032\u0019"+ + "\u0000\u01daa\u0001\u0000\u0000\u0000\u01db\u01dc\u0005\u0010\u0000\u0000"+ + "\u01dc\u01df\u0003*\u0015\u0000\u01dd\u01de\u0005;\u0000\u0000\u01de\u01e0"+ + "\u0003\u000e\u0007\u0000\u01df\u01dd\u0001\u0000\u0000\u0000\u01df\u01e0"+ + "\u0001\u0000\u0000\u0000\u01e0c\u0001\u0000\u0000\u0000\u01e1\u01e2\u0005"+ + "\u0004\u0000\u0000\u01e2\u01e5\u0003.\u0017\u0000\u01e3\u01e4\u0005J\u0000"+ + "\u0000\u01e4\u01e6\u0003.\u0017\u0000\u01e5\u01e3\u0001\u0000\u0000\u0000"+ + "\u01e5\u01e6\u0001\u0000\u0000\u0000\u01e6\u01ec\u0001\u0000\u0000\u0000"+ + "\u01e7\u01e8\u0005\u0084\u0000\u0000\u01e8\u01e9\u0003.\u0017\u0000\u01e9"+ + "\u01ea\u0005>\u0000\u0000\u01ea\u01eb\u0003.\u0017\u0000\u01eb\u01ed\u0001"+ + "\u0000\u0000\u0000\u01ec\u01e7\u0001\u0000\u0000\u0000\u01ec\u01ed\u0001"+ + "\u0000\u0000\u0000\u01ede\u0001\u0000\u0000\u0000\u01ee\u01ef\u0005\u001e"+ + "\u0000\u0000\u01ef\u01f0\u00032\u0019\u0000\u01f0g\u0001\u0000\u0000\u0000"+ + "\u01f1\u01f2\u0005\u0015\u0000\u0000\u01f2\u01f3\u0003j5\u0000\u01f3i"+ + "\u0001\u0000\u0000\u0000\u01f4\u01f6\u0003l6\u0000\u01f5\u01f4\u0001\u0000"+ + "\u0000\u0000\u01f6\u01f7\u0001\u0000\u0000\u0000\u01f7\u01f5\u0001\u0000"+ + "\u0000\u0000\u01f7\u01f8\u0001\u0000\u0000\u0000\u01f8k\u0001\u0000\u0000"+ + "\u0000\u01f9\u01fa\u0005c\u0000\u0000\u01fa\u01fb\u0003n7\u0000\u01fb"+ + "\u01fc\u0005d\u0000\u0000\u01fcm\u0001\u0000\u0000\u0000\u01fd\u01fe\u0006"+ + "7\uffff\uffff\u0000\u01fe\u01ff\u0003p8\u0000\u01ff\u0205\u0001\u0000"+ + "\u0000\u0000\u0200\u0201\n\u0001\u0000\u0000\u0201\u0202\u00054\u0000"+ + "\u0000\u0202\u0204\u0003p8\u0000\u0203\u0200\u0001\u0000\u0000\u0000\u0204"+ + "\u0207\u0001\u0000\u0000\u0000\u0205\u0203\u0001\u0000\u0000\u0000\u0205"+ + "\u0206\u0001\u0000\u0000\u0000\u0206o\u0001\u0000\u0000\u0000\u0207\u0205"+ + "\u0001\u0000\u0000\u0000\u0208\u020f\u0003&\u0013\u0000\u0209\u020f\u0003"+ + "\b\u0004\u0000\u020a\u020f\u0003>\u001f\u0000\u020b\u020f\u0003(\u0014"+ + "\u0000\u020c\u020f\u0003@ \u0000\u020d\u020f\u0003L&\u0000\u020e\u0208"+ + "\u0001\u0000\u0000\u0000\u020e\u0209\u0001\u0000\u0000\u0000\u020e\u020a"+ + "\u0001\u0000\u0000\u0000\u020e\u020b\u0001\u0000\u0000\u0000\u020e\u020c"+ + "\u0001\u0000\u0000\u0000\u020e\u020d\u0001\u0000\u0000\u0000\u020fq\u0001"+ + "\u0000\u0000\u0000\u0210\u0211\u0005\u001f\u0000\u0000\u0211s\u0001\u0000"+ + "\u0000\u0000\u0212\u0213\u0005\u0011\u0000\u0000\u0213\u0214\u0003\u008e"+ + "G\u0000\u0214\u0215\u0005J\u0000\u0000\u0215\u0218\u0003\u0012\t\u0000"+ + "\u0216\u0217\u0005O\u0000\u0000\u0217\u0219\u0003<\u001e\u0000\u0218\u0216"+ + "\u0001\u0000\u0000\u0000\u0218\u0219\u0001\u0000\u0000\u0000\u0219u\u0001"+ + "\u0000\u0000\u0000\u021a\u021e\u0005\u0007\u0000\u0000\u021b\u021c\u0003"+ + ".\u0017\u0000\u021c\u021d\u0005:\u0000\u0000\u021d\u021f\u0001\u0000\u0000"+ + "\u0000\u021e\u021b\u0001\u0000\u0000\u0000\u021e\u021f\u0001\u0000\u0000"+ + "\u0000\u021f\u0220\u0001\u0000\u0000\u0000\u0220\u0221\u0003\u0084B\u0000"+ + "\u0221\u0222\u0005O\u0000\u0000\u0222\u0223\u0003<\u001e\u0000\u0223w"+ + "\u0001\u0000\u0000\u0000\u0224\u0225\u0005\u0012\u0000\u0000\u0225\u0226"+ + "\u0003\u0094J\u0000\u0226y\u0001\u0000\u0000\u0000\u0227\u0228\u0006="+ + "\uffff\uffff\u0000\u0228\u0229\u0005G\u0000\u0000\u0229\u0245\u0003z="+ + "\b\u022a\u0245\u0003\u0080@\u0000\u022b\u0245\u0003|>\u0000\u022c\u022e"+ + "\u0003\u0080@\u0000\u022d\u022f\u0005G\u0000\u0000\u022e\u022d\u0001\u0000"+ + "\u0000\u0000\u022e\u022f\u0001\u0000\u0000\u0000\u022f\u0230\u0001\u0000"+ + "\u0000\u0000\u0230\u0231\u0005C\u0000\u0000\u0231\u0232\u0005c\u0000\u0000"+ + "\u0232\u0237\u0003\u0080@\u0000\u0233\u0234\u0005>\u0000\u0000\u0234\u0236"+ + "\u0003\u0080@\u0000\u0235\u0233\u0001\u0000\u0000\u0000\u0236\u0239\u0001"+ + "\u0000\u0000\u0000\u0237\u0235\u0001\u0000\u0000\u0000\u0237\u0238\u0001"+ + "\u0000\u0000\u0000\u0238\u023a\u0001\u0000\u0000\u0000\u0239\u0237\u0001"+ + "\u0000\u0000\u0000\u023a\u023b\u0005d\u0000\u0000\u023b\u0245\u0001\u0000"+ + "\u0000\u0000\u023c\u023d\u0003\u0080@\u0000\u023d\u023f\u0005D\u0000\u0000"+ + "\u023e\u0240\u0005G\u0000\u0000\u023f\u023e\u0001\u0000\u0000\u0000\u023f"+ + "\u0240\u0001\u0000\u0000\u0000\u0240\u0241\u0001\u0000\u0000\u0000\u0241"+ + "\u0242\u0005H\u0000\u0000\u0242\u0245\u0001\u0000\u0000\u0000\u0243\u0245"+ + "\u0003~?\u0000\u0244\u0227\u0001\u0000\u0000\u0000\u0244\u022a\u0001\u0000"+ + "\u0000\u0000\u0244\u022b\u0001\u0000\u0000\u0000\u0244\u022c\u0001\u0000"+ + "\u0000\u0000\u0244\u023c\u0001\u0000\u0000\u0000\u0244\u0243\u0001\u0000"+ + "\u0000\u0000\u0245\u024e\u0001\u0000\u0000\u0000\u0246\u0247\n\u0005\u0000"+ + "\u0000\u0247\u0248\u00058\u0000\u0000\u0248\u024d\u0003z=\u0006\u0249"+ + "\u024a\n\u0004\u0000\u0000\u024a\u024b\u0005K\u0000\u0000\u024b\u024d"+ + "\u0003z=\u0005\u024c\u0246\u0001\u0000\u0000\u0000\u024c\u0249\u0001\u0000"+ + "\u0000\u0000\u024d\u0250\u0001\u0000\u0000\u0000\u024e\u024c\u0001\u0000"+ + "\u0000\u0000\u024e\u024f\u0001\u0000\u0000\u0000\u024f{\u0001\u0000\u0000"+ + "\u0000\u0250\u024e\u0001\u0000\u0000\u0000\u0251\u0253\u0003\u0080@\u0000"+ + "\u0252\u0254\u0005G\u0000\u0000\u0253\u0252\u0001\u0000\u0000\u0000\u0253"+ + "\u0254\u0001\u0000\u0000\u0000\u0254\u0255\u0001\u0000\u0000\u0000\u0255"+ + "\u0256\u0005F\u0000\u0000\u0256\u0257\u0003\u0098L\u0000\u0257\u0260\u0001"+ + "\u0000\u0000\u0000\u0258\u025a\u0003\u0080@\u0000\u0259\u025b\u0005G\u0000"+ + "\u0000\u025a\u0259\u0001\u0000\u0000\u0000\u025a\u025b\u0001\u0000\u0000"+ + "\u0000\u025b\u025c\u0001\u0000\u0000\u0000\u025c\u025d\u0005M\u0000\u0000"+ + "\u025d\u025e\u0003\u0098L\u0000\u025e\u0260\u0001\u0000\u0000\u0000\u025f"+ + "\u0251\u0001\u0000\u0000\u0000\u025f\u0258\u0001\u0000\u0000\u0000\u0260"+ + "}\u0001\u0000\u0000\u0000\u0261\u0264\u0003.\u0017\u0000\u0262\u0263\u0005"+ + "<\u0000\u0000\u0263\u0265\u0003\n\u0005\u0000\u0264\u0262\u0001\u0000"+ + "\u0000\u0000\u0264\u0265\u0001\u0000\u0000\u0000\u0265\u0266\u0001\u0000"+ + "\u0000\u0000\u0266\u0267\u0005=\u0000\u0000\u0267\u0268\u0003\u008eG\u0000"+ + "\u0268\u007f\u0001\u0000\u0000\u0000\u0269\u026f\u0003\u0082A\u0000\u026a"+ + "\u026b\u0003\u0082A\u0000\u026b\u026c\u0003\u009aM\u0000\u026c\u026d\u0003"+ + "\u0082A\u0000\u026d\u026f\u0001\u0000\u0000\u0000\u026e\u0269\u0001\u0000"+ + "\u0000\u0000\u026e\u026a\u0001\u0000\u0000\u0000\u026f\u0081\u0001\u0000"+ + "\u0000\u0000\u0270\u0271\u0006A\uffff\uffff\u0000\u0271\u0275\u0003\u0084"+ + "B\u0000\u0272\u0273\u0007\u0004\u0000\u0000\u0273\u0275\u0003\u0082A\u0003"+ + "\u0274\u0270\u0001\u0000\u0000\u0000\u0274\u0272\u0001\u0000\u0000\u0000"+ + "\u0275\u027e\u0001\u0000\u0000\u0000\u0276\u0277\n\u0002\u0000\u0000\u0277"+ + "\u0278\u0007\u0005\u0000\u0000\u0278\u027d\u0003\u0082A\u0003\u0279\u027a"+ + "\n\u0001\u0000\u0000\u027a\u027b\u0007\u0004\u0000\u0000\u027b\u027d\u0003"+ + "\u0082A\u0002\u027c\u0276\u0001\u0000\u0000\u0000\u027c\u0279\u0001\u0000"+ + "\u0000\u0000\u027d\u0280\u0001\u0000\u0000\u0000\u027e\u027c\u0001\u0000"+ + "\u0000\u0000\u027e\u027f\u0001\u0000\u0000\u0000\u027f\u0083\u0001\u0000"+ + "\u0000\u0000\u0280\u027e\u0001\u0000\u0000\u0000\u0281\u0282\u0006B\uffff"+ + "\uffff\u0000\u0282\u028a\u0003\u008eG\u0000\u0283\u028a\u0003.\u0017\u0000"+ + "\u0284\u028a\u0003\u0086C\u0000\u0285\u0286\u0005c\u0000\u0000\u0286\u0287"+ + "\u0003z=\u0000\u0287\u0288\u0005d\u0000\u0000\u0288\u028a\u0001\u0000"+ + "\u0000\u0000\u0289\u0281\u0001\u0000\u0000\u0000\u0289\u0283\u0001\u0000"+ + "\u0000\u0000\u0289\u0284\u0001\u0000\u0000\u0000\u0289\u0285\u0001\u0000"+ + "\u0000\u0000\u028a\u0290\u0001\u0000\u0000\u0000\u028b\u028c\n\u0001\u0000"+ + "\u0000\u028c\u028d\u0005<\u0000\u0000\u028d\u028f\u0003\n\u0005\u0000"+ + "\u028e\u028b\u0001\u0000\u0000\u0000\u028f\u0292\u0001\u0000\u0000\u0000"+ + "\u0290\u028e\u0001\u0000\u0000\u0000\u0290\u0291\u0001\u0000\u0000\u0000"+ + "\u0291\u0085\u0001\u0000\u0000\u0000\u0292\u0290\u0001\u0000\u0000\u0000"+ + "\u0293\u0294\u0003\u0088D\u0000\u0294\u02a2\u0005c\u0000\u0000\u0295\u02a3"+ + "\u0005Y\u0000\u0000\u0296\u029b\u0003z=\u0000\u0297\u0298\u0005>\u0000"+ + "\u0000\u0298\u029a\u0003z=\u0000\u0299\u0297\u0001\u0000\u0000\u0000\u029a"+ + "\u029d\u0001\u0000\u0000\u0000\u029b\u0299\u0001\u0000\u0000\u0000\u029b"+ + "\u029c\u0001\u0000\u0000\u0000\u029c\u02a0\u0001\u0000\u0000\u0000\u029d"+ + "\u029b\u0001\u0000\u0000\u0000\u029e\u029f\u0005>\u0000\u0000\u029f\u02a1"+ + "\u0003\u008aE\u0000\u02a0\u029e\u0001\u0000\u0000\u0000\u02a0\u02a1\u0001"+ + "\u0000\u0000\u0000\u02a1\u02a3\u0001\u0000\u0000\u0000\u02a2\u0295\u0001"+ + "\u0000\u0000\u0000\u02a2\u0296\u0001\u0000\u0000\u0000\u02a2\u02a3\u0001"+ + "\u0000\u0000\u0000\u02a3\u02a4\u0001\u0000\u0000\u0000\u02a4\u02a5\u0005"+ + "d\u0000\u0000\u02a5\u0087\u0001\u0000\u0000\u0000\u02a6\u02a7\u0003<\u001e"+ + "\u0000\u02a7\u0089\u0001\u0000\u0000\u0000\u02a8\u02a9\u0005\\\u0000\u0000"+ + "\u02a9\u02ae\u0003\u008cF\u0000\u02aa\u02ab\u0005>\u0000\u0000\u02ab\u02ad"+ + "\u0003\u008cF\u0000\u02ac\u02aa\u0001\u0000\u0000\u0000\u02ad\u02b0\u0001"+ + "\u0000\u0000\u0000\u02ae\u02ac\u0001\u0000\u0000\u0000\u02ae\u02af\u0001"+ + "\u0000\u0000\u0000\u02af\u02b1\u0001\u0000\u0000\u0000\u02b0\u02ae\u0001"+ + "\u0000\u0000\u0000\u02b1\u02b2\u0005]\u0000\u0000\u02b2\u008b\u0001\u0000"+ + "\u0000\u0000\u02b3\u02b4\u0003\u0098L\u0000\u02b4\u02b5\u0005=\u0000\u0000"+ + "\u02b5\u02b6\u0003\u008eG\u0000\u02b6\u008d\u0001\u0000\u0000\u0000\u02b7"+ + "\u02e2\u0005H\u0000\u0000\u02b8\u02b9\u0003\u0096K\u0000\u02b9\u02ba\u0005"+ + "e\u0000\u0000\u02ba\u02e2\u0001\u0000\u0000\u0000\u02bb\u02e2\u0003\u0094"+ + "J\u0000\u02bc\u02e2\u0003\u0096K\u0000\u02bd\u02e2\u0003\u0090H\u0000"+ + "\u02be\u02e2\u00038\u001c\u0000\u02bf\u02e2\u0003\u0098L\u0000\u02c0\u02c1"+ + "\u0005a\u0000\u0000\u02c1\u02c6\u0003\u0092I\u0000\u02c2\u02c3\u0005>"+ + "\u0000\u0000\u02c3\u02c5\u0003\u0092I\u0000\u02c4\u02c2\u0001\u0000\u0000"+ + "\u0000\u02c5\u02c8\u0001\u0000\u0000\u0000\u02c6\u02c4\u0001\u0000\u0000"+ + "\u0000\u02c6\u02c7\u0001\u0000\u0000\u0000\u02c7\u02c9\u0001\u0000\u0000"+ + "\u0000\u02c8\u02c6\u0001\u0000\u0000\u0000\u02c9\u02ca\u0005b\u0000\u0000"+ + "\u02ca\u02e2\u0001\u0000\u0000\u0000\u02cb\u02cc\u0005a\u0000\u0000\u02cc"+ + "\u02d1\u0003\u0090H\u0000\u02cd\u02ce\u0005>\u0000\u0000\u02ce\u02d0\u0003"+ + "\u0090H\u0000\u02cf\u02cd\u0001\u0000\u0000\u0000\u02d0\u02d3\u0001\u0000"+ + "\u0000\u0000\u02d1\u02cf\u0001\u0000\u0000\u0000\u02d1\u02d2\u0001\u0000"+ + "\u0000\u0000\u02d2\u02d4\u0001\u0000\u0000\u0000\u02d3\u02d1\u0001\u0000"+ + "\u0000\u0000\u02d4\u02d5\u0005b\u0000\u0000\u02d5\u02e2\u0001\u0000\u0000"+ + "\u0000\u02d6\u02d7\u0005a\u0000\u0000\u02d7\u02dc\u0003\u0098L\u0000\u02d8"+ + "\u02d9\u0005>\u0000\u0000\u02d9\u02db\u0003\u0098L\u0000\u02da\u02d8\u0001"+ + "\u0000\u0000\u0000\u02db\u02de\u0001\u0000\u0000\u0000\u02dc\u02da\u0001"+ + "\u0000\u0000\u0000\u02dc\u02dd\u0001\u0000\u0000\u0000\u02dd\u02df\u0001"+ + "\u0000\u0000\u0000\u02de\u02dc\u0001\u0000\u0000\u0000\u02df\u02e0\u0005"+ + "b\u0000\u0000\u02e0\u02e2\u0001\u0000\u0000\u0000\u02e1\u02b7\u0001\u0000"+ + "\u0000\u0000\u02e1\u02b8\u0001\u0000\u0000\u0000\u02e1\u02bb\u0001\u0000"+ + "\u0000\u0000\u02e1\u02bc\u0001\u0000\u0000\u0000\u02e1\u02bd\u0001\u0000"+ + "\u0000\u0000\u02e1\u02be\u0001\u0000\u0000\u0000\u02e1\u02bf\u0001\u0000"+ + "\u0000\u0000\u02e1\u02c0\u0001\u0000\u0000\u0000\u02e1\u02cb\u0001\u0000"+ + "\u0000\u0000\u02e1\u02d6\u0001\u0000\u0000\u0000\u02e2\u008f\u0001\u0000"+ + "\u0000\u0000\u02e3\u02e4\u0007\u0006\u0000\u0000\u02e4\u0091\u0001\u0000"+ + "\u0000\u0000\u02e5\u02e8\u0003\u0094J\u0000\u02e6\u02e8\u0003\u0096K\u0000"+ + "\u02e7\u02e5\u0001\u0000\u0000\u0000\u02e7\u02e6\u0001\u0000\u0000\u0000"+ + "\u02e8\u0093\u0001\u0000\u0000\u0000\u02e9\u02eb\u0007\u0004\u0000\u0000"+ + "\u02ea\u02e9\u0001\u0000\u0000\u0000\u02ea\u02eb\u0001\u0000\u0000\u0000"+ + "\u02eb\u02ec\u0001\u0000\u0000\u0000\u02ec\u02ed\u00057\u0000\u0000\u02ed"+ + "\u0095\u0001\u0000\u0000\u0000\u02ee\u02f0\u0007\u0004\u0000\u0000\u02ef"+ + "\u02ee\u0001\u0000\u0000\u0000\u02ef\u02f0\u0001\u0000\u0000\u0000\u02f0"+ + "\u02f1\u0001\u0000\u0000\u0000\u02f1\u02f2\u00056\u0000\u0000\u02f2\u0097"+ + "\u0001\u0000\u0000\u0000\u02f3\u02f4\u00055\u0000\u0000\u02f4\u0099\u0001"+ + "\u0000\u0000\u0000\u02f5\u02f6\u0007\u0007\u0000\u0000\u02f6\u009b\u0001"+ + "\u0000\u0000\u0000\u02f7\u02f8\u0007\b\u0000\u0000\u02f8\u02f9\u0005r"+ + "\u0000\u0000\u02f9\u02fa\u0003\u009eO\u0000\u02fa\u02fb\u0003\u00a0P\u0000"+ + "\u02fb\u009d\u0001\u0000\u0000\u0000\u02fc\u02fd\u0003\u001c\u000e\u0000"+ + "\u02fd\u009f\u0001\u0000\u0000\u0000\u02fe\u02ff\u0005J\u0000\u0000\u02ff"+ + "\u0304\u0003\u00a2Q\u0000\u0300\u0301\u0005>\u0000\u0000\u0301\u0303\u0003"+ + "\u00a2Q\u0000\u0302\u0300\u0001\u0000\u0000\u0000\u0303\u0306\u0001\u0000"+ + "\u0000\u0000\u0304\u0302\u0001\u0000\u0000\u0000\u0304\u0305\u0001\u0000"+ + "\u0000\u0000\u0305\u00a1\u0001\u0000\u0000\u0000\u0306\u0304\u0001\u0000"+ + "\u0000\u0000\u0307\u0308\u0003\u0080@\u0000\u0308\u00a3\u0001\u0000\u0000"+ + "\u0000G\u00af\u00b8\u00d7\u00e6\u00ec\u00f5\u00fb\u0108\u010c\u0111\u0117"+ "\u0119\u0127\u012f\u0133\u013a\u0140\u0147\u014f\u0157\u015f\u0163\u0167"+ - "\u016c\u0177\u017c\u0180\u018e\u0199\u01a7\u01bc\u01c4\u01c7\u01cc\u01d9"+ - "\u01df\u01e6\u01f1\u01ff\u0208\u0212\u0218\u0228\u0231\u0239\u023e\u0246"+ - "\u0248\u024d\u0254\u0259\u025e\u0268\u026e\u0276\u0278\u0283\u028a\u0295"+ - "\u029a\u029c\u02a8\u02c0\u02cb\u02d6\u02db\u02e1\u02e4\u02e9\u02fe"; + "\u016c\u0177\u017c\u0180\u018e\u0199\u019f\u01ad\u01c2\u01ca\u01cd\u01d2"+ + "\u01df\u01e5\u01ec\u01f7\u0205\u020e\u0218\u021e\u022e\u0237\u023f\u0244"+ + "\u024c\u024e\u0253\u025a\u025f\u0264\u026e\u0274\u027c\u027e\u0289\u0290"+ + "\u029b\u02a0\u02a2\u02ae\u02c6\u02d1\u02dc\u02e1\u02e7\u02ea\u02ef\u0304"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/ExpressionTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/ExpressionTests.java index 384b80c16c78b..aebd847aec44e 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/ExpressionTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/ExpressionTests.java @@ -585,7 +585,7 @@ public void testProjectRename() { String[] oldName = new String[] { "b", "a.c", "x.y", "a" }; List renamings; for (int i = 0; i < newName.length; i++) { - Rename r = renameExpression(oldName[i] + " AS " + newName[i]); + Rename r = renameExpression(randomBoolean() ? (oldName[i] + " AS " + newName[i]) : (newName[i] + " = " + oldName[i])); renamings = r.renamings(); assertThat(renamings.size(), equalTo(1)); assertThat(renamings.get(0), instanceOf(Alias.class)); @@ -612,6 +612,7 @@ public void testMultipleProjectPatterns() { public void testForbidWildcardProjectRename() { assertParsingException(() -> renameExpression("b* AS a*"), "line 1:17: Using wildcards [*] in RENAME is not allowed [b* AS a*]"); + assertParsingException(() -> renameExpression("a* = b*"), "line 1:17: Using wildcards [*] in RENAME is not allowed [a* = b*]"); } public void testSimplifyInWithSingleElementList() { From 1a9e67296089242ad02c80387e97da31900eb7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Wed, 11 Jun 2025 13:21:55 +0200 Subject: [PATCH 061/102] [DOCS] Adds preview tag to the CHANGE_POINT ES|QL command in the command list. (#129247) --- .../query-languages/esql/_snippets/lists/processing-commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/query-languages/esql/_snippets/lists/processing-commands.md b/docs/reference/query-languages/esql/_snippets/lists/processing-commands.md index 8e4a83fa46cff..fefb0dcc84b0c 100644 --- a/docs/reference/query-languages/esql/_snippets/lists/processing-commands.md +++ b/docs/reference/query-languages/esql/_snippets/lists/processing-commands.md @@ -1,4 +1,4 @@ -* [`CHANGE_POINT`](../../commands/processing-commands.md#esql-change_point) +* [preview] [`CHANGE_POINT`](../../commands/processing-commands.md#esql-change_point) * [`DISSECT`](../../commands/processing-commands.md#esql-dissect) * [`DROP`](../../commands/processing-commands.md#esql-drop) * [`ENRICH`](../../commands/processing-commands.md#esql-enrich) From 3e79b2a9ae72ed8116f81c01984ef369a0dc0b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Wed, 11 Jun 2025 13:59:59 +0200 Subject: [PATCH 062/102] ESQL: Skip unused STATS groups by adding a Top N BlockHash implementation (#127148) - Add a new `LongTopNBlockHash` implementation taking care of skipping unused values. - Add a `TopNUniqueSet` to take care of storing the top N values (without nulls). - Add a `TopNMultivalueDedupeLong` class helping with it (An adaptation of the existing `MultivalueDedupeLong`). - Add some tests to `HashAggregationOperator`. It wasn't changed much, but helps a bit with the E2E. - Add MicroBenchmarks for TopN groupings, to ensure we're actually improving things with this. --- .../compute/operator/AggregatorBenchmark.java | 45 +- docs/changelog/127148.yaml | 5 + .../common/util/BinarySearcher.java | 2 +- .../blockhash/BytesRefBlockHash.java | 9 + .../blockhash/DoubleBlockHash.java | 9 + .../aggregation/blockhash/IntBlockHash.java | 9 + .../aggregation/blockhash/LongBlockHash.java | 9 + .../aggregation/blockhash/BlockHash.java | 29 +- .../blockhash/LongTopNBlockHash.java | 323 +++++++++ .../aggregation/blockhash/X-BlockHash.java.st | 9 + .../compute/data/sort/LongTopNSet.java | 173 +++++ .../mvdedupe/TopNMultivalueDedupeLong.java | 414 +++++++++++ .../blockhash/BlockHashTestCase.java | 202 ++++++ .../aggregation/blockhash/BlockHashTests.java | 651 +++++++----------- .../blockhash/TopNBlockHashTests.java | 393 +++++++++++ .../compute/data/sort/LongTopNSetTests.java | 52 ++ .../compute/data/sort/TopNSetTestCase.java | 215 ++++++ .../HashAggregationOperatorTests.java | 158 +++++ .../AbstractPhysicalOperationProviders.java | 2 +- 19 files changed, 2290 insertions(+), 419 deletions(-) create mode 100644 docs/changelog/127148.yaml create mode 100644 x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongTopNBlockHash.java create mode 100644 x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/sort/LongTopNSet.java create mode 100644 x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/mvdedupe/TopNMultivalueDedupeLong.java create mode 100644 x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/TopNBlockHashTests.java create mode 100644 x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/LongTopNSetTests.java create mode 100644 x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/AggregatorBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/AggregatorBenchmark.java index c6ce939786535..d144d7601349d 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/AggregatorBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/AggregatorBenchmark.java @@ -73,6 +73,7 @@ public class AggregatorBenchmark { static final int BLOCK_LENGTH = 8 * 1024; private static final int OP_COUNT = 1024; private static final int GROUPS = 5; + private static final int TOP_N_LIMIT = 3; private static final BlockFactory blockFactory = BlockFactory.getInstance( new NoopCircuitBreaker("noop"), @@ -90,6 +91,7 @@ public class AggregatorBenchmark { private static final String TWO_ORDINALS = "two_" + ORDINALS; private static final String LONGS_AND_BYTES_REFS = LONGS + "_and_" + BYTES_REFS; private static final String TWO_LONGS_AND_BYTES_REFS = "two_" + LONGS + "_and_" + BYTES_REFS; + private static final String TOP_N_LONGS = "top_n_" + LONGS; private static final String VECTOR_DOUBLES = "vector_doubles"; private static final String HALF_NULL_DOUBLES = "half_null_doubles"; @@ -147,7 +149,8 @@ static void selfTest() { TWO_BYTES_REFS, TWO_ORDINALS, LONGS_AND_BYTES_REFS, - TWO_LONGS_AND_BYTES_REFS } + TWO_LONGS_AND_BYTES_REFS, + TOP_N_LONGS } ) public String grouping; @@ -161,8 +164,7 @@ static void selfTest() { public String filter; private static Operator operator(DriverContext driverContext, String grouping, String op, String dataType, String filter) { - - if (grouping.equals("none")) { + if (grouping.equals(NONE)) { return new AggregationOperator( List.of(supplier(op, dataType, filter).aggregatorFactory(AggregatorMode.SINGLE, List.of(0)).apply(driverContext)), driverContext @@ -188,6 +190,9 @@ private static Operator operator(DriverContext driverContext, String grouping, S new BlockHash.GroupSpec(1, ElementType.LONG), new BlockHash.GroupSpec(2, ElementType.BYTES_REF) ); + case TOP_N_LONGS -> List.of( + new BlockHash.GroupSpec(0, ElementType.LONG, false, new BlockHash.TopNDef(0, true, true, TOP_N_LIMIT)) + ); default -> throw new IllegalArgumentException("unsupported grouping [" + grouping + "]"); }; return new HashAggregationOperator( @@ -271,10 +276,14 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri case BOOLEANS -> 2; default -> GROUPS; }; + int availableGroups = switch (grouping) { + case TOP_N_LONGS -> TOP_N_LIMIT; + default -> groups; + }; switch (op) { case AVG -> { DoubleBlock dValues = (DoubleBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long sum = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).sum(); long count = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).count(); @@ -286,7 +295,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri } case COUNT -> { LongBlock lValues = (LongBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long expected = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).count() * opCount; if (lValues.getLong(g) != expected) { @@ -296,7 +305,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri } case COUNT_DISTINCT -> { LongBlock lValues = (LongBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long expected = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).distinct().count(); long count = lValues.getLong(g); @@ -310,7 +319,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri switch (dataType) { case LONGS -> { LongBlock lValues = (LongBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { if (lValues.getLong(g) != (long) g) { throw new AssertionError(prefix + "expected [" + g + "] but was [" + lValues.getLong(g) + "]"); } @@ -318,7 +327,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri } case DOUBLES -> { DoubleBlock dValues = (DoubleBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { if (dValues.getDouble(g) != (long) g) { throw new AssertionError(prefix + "expected [" + g + "] but was [" + dValues.getDouble(g) + "]"); } @@ -331,7 +340,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri switch (dataType) { case LONGS -> { LongBlock lValues = (LongBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long expected = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).max().getAsLong(); if (lValues.getLong(g) != expected) { @@ -341,7 +350,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri } case DOUBLES -> { DoubleBlock dValues = (DoubleBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long expected = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).max().getAsLong(); if (dValues.getDouble(g) != expected) { @@ -356,7 +365,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri switch (dataType) { case LONGS -> { LongBlock lValues = (LongBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long expected = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).sum() * opCount; if (lValues.getLong(g) != expected) { @@ -366,7 +375,7 @@ private static void checkGrouped(String prefix, String grouping, String op, Stri } case DOUBLES -> { DoubleBlock dValues = (DoubleBlock) values; - for (int g = 0; g < groups; g++) { + for (int g = 0; g < availableGroups; g++) { long group = g; long expected = LongStream.range(0, BLOCK_LENGTH).filter(l -> l % groups == group).sum() * opCount; if (dValues.getDouble(g) != expected) { @@ -391,6 +400,14 @@ private static void checkGroupingBlock(String prefix, String grouping, Block blo } } } + case TOP_N_LONGS -> { + LongBlock groups = (LongBlock) block; + for (int g = 0; g < TOP_N_LIMIT; g++) { + if (groups.getLong(g) != (long) g) { + throw new AssertionError(prefix + "bad group expected [" + g + "] but was [" + groups.getLong(g) + "]"); + } + } + } case INTS -> { IntBlock groups = (IntBlock) block; for (int g = 0; g < GROUPS; g++) { @@ -495,7 +512,7 @@ private static void checkUngrouped(String prefix, String op, String dataType, Pa private static Page page(BlockFactory blockFactory, String grouping, String blockType) { Block dataBlock = dataBlock(blockFactory, blockType); - if (grouping.equals("none")) { + if (grouping.equals(NONE)) { return new Page(dataBlock); } List blocks = groupingBlocks(grouping, blockType); @@ -564,7 +581,7 @@ private static Block groupingBlock(String grouping, String blockType) { default -> throw new UnsupportedOperationException("bad grouping [" + grouping + "]"); }; return switch (grouping) { - case LONGS -> { + case TOP_N_LONGS, LONGS -> { var builder = blockFactory.newLongBlockBuilder(BLOCK_LENGTH); for (int i = 0; i < BLOCK_LENGTH; i++) { for (int v = 0; v < valuesPerGroup; v++) { diff --git a/docs/changelog/127148.yaml b/docs/changelog/127148.yaml new file mode 100644 index 0000000000000..db98b21a944b2 --- /dev/null +++ b/docs/changelog/127148.yaml @@ -0,0 +1,5 @@ +pr: 127148 +summary: Skip unused STATS groups by adding a Top N `BlockHash` implementation +area: ES|QL +type: enhancement +issues: [] diff --git a/server/src/main/java/org/elasticsearch/common/util/BinarySearcher.java b/server/src/main/java/org/elasticsearch/common/util/BinarySearcher.java index 173399619017e..ea39060447e12 100644 --- a/server/src/main/java/org/elasticsearch/common/util/BinarySearcher.java +++ b/server/src/main/java/org/elasticsearch/common/util/BinarySearcher.java @@ -38,7 +38,7 @@ public abstract class BinarySearcher { /** * @return the index who's underlying value is closest to the value being searched for. */ - private int getClosestIndex(int index1, int index2) { + protected int getClosestIndex(int index1, int index2) { if (distance(index1) < distance(index2)) { return index1; } else { diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/BytesRefBlockHash.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/BytesRefBlockHash.java index 9297e76d5c0be..834585f5475a7 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/BytesRefBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/BytesRefBlockHash.java @@ -74,6 +74,9 @@ public void add(Page page, GroupingAggregatorFunction.AddInput addInput) { } } + /** + * Adds the vector values to the hash, and returns a new vector with the group IDs for those positions. + */ IntVector add(BytesRefVector vector) { var ordinals = vector.asOrdinals(); if (ordinals != null) { @@ -90,6 +93,12 @@ IntVector add(BytesRefVector vector) { } } + /** + * Adds the block values to the hash, and returns a new vector with the group IDs for those positions. + *

+ * For nulls, a 0 group ID is used. For multivalues, a multivalue is used with all the group IDs. + *

+ */ IntBlock add(BytesRefBlock block) { var ordinals = block.asOrdinals(); if (ordinals != null) { diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java index ede268271d9dd..48fa60242b459 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java @@ -73,6 +73,9 @@ public void add(Page page, GroupingAggregatorFunction.AddInput addInput) { } } + /** + * Adds the vector values to the hash, and returns a new vector with the group IDs for those positions. + */ IntVector add(DoubleVector vector) { int positions = vector.getPositionCount(); try (var builder = blockFactory.newIntVectorFixedBuilder(positions)) { @@ -84,6 +87,12 @@ IntVector add(DoubleVector vector) { } } + /** + * Adds the block values to the hash, and returns a new vector with the group IDs for those positions. + *

+ * For nulls, a 0 group ID is used. For multivalues, a multivalue is used with all the group IDs. + *

+ */ IntBlock add(DoubleBlock block) { MultivalueDedupe.HashResult result = new MultivalueDedupeDouble(block).hashAdd(blockFactory, hash); seenNull |= result.sawNull(); diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java index 3a3267af3c983..40c49e31ea349 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java @@ -71,6 +71,9 @@ public void add(Page page, GroupingAggregatorFunction.AddInput addInput) { } } + /** + * Adds the vector values to the hash, and returns a new vector with the group IDs for those positions. + */ IntVector add(IntVector vector) { int positions = vector.getPositionCount(); try (var builder = blockFactory.newIntVectorFixedBuilder(positions)) { @@ -82,6 +85,12 @@ IntVector add(IntVector vector) { } } + /** + * Adds the block values to the hash, and returns a new vector with the group IDs for those positions. + *

+ * For nulls, a 0 group ID is used. For multivalues, a multivalue is used with all the group IDs. + *

+ */ IntBlock add(IntBlock block) { MultivalueDedupe.HashResult result = new MultivalueDedupeInt(block).hashAdd(blockFactory, hash); seenNull |= result.sawNull(); diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java index c5152ae3cd22d..a260b52eb2642 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java @@ -73,6 +73,9 @@ public void add(Page page, GroupingAggregatorFunction.AddInput addInput) { } } + /** + * Adds the vector values to the hash, and returns a new vector with the group IDs for those positions. + */ IntVector add(LongVector vector) { int positions = vector.getPositionCount(); try (var builder = blockFactory.newIntVectorFixedBuilder(positions)) { @@ -84,6 +87,12 @@ IntVector add(LongVector vector) { } } + /** + * Adds the block values to the hash, and returns a new vector with the group IDs for those positions. + *

+ * For nulls, a 0 group ID is used. For multivalues, a multivalue is used with all the group IDs. + *

+ */ IntBlock add(LongBlock block) { MultivalueDedupe.HashResult result = new MultivalueDedupeLong(block).hashAdd(blockFactory, hash); seenNull |= result.sawNull(); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BlockHash.java index 191d6443264ca..1cae296f09c02 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BlockHash.java @@ -23,6 +23,7 @@ import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.data.Page; +import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Releasable; import org.elasticsearch.core.ReleasableIterator; import org.elasticsearch.index.analysis.AnalysisRegistry; @@ -113,13 +114,30 @@ public abstract class BlockHash implements Releasable, SeenGroupIds { @Override public abstract BitArray seenGroupIds(BigArrays bigArrays); + /** + * Configuration for a BlockHash group spec that is later sorted and limited (Top-N). + *

+ * Part of a performance improvement to avoid aggregating groups that will not be used. + *

+ * + * @param order The order of this group in the sort, starting at 0 + * @param asc True if this group will be sorted ascending. False if descending. + * @param nullsFirst True if the nulls should be the first elements in the TopN. False if they should be kept last. + * @param limit The number of elements to keep, including nulls. + */ + public record TopNDef(int order, boolean asc, boolean nullsFirst, int limit) {} + /** * @param isCategorize Whether this group is a CATEGORIZE() or not. * May be changed in the future when more stateful grouping functions are added. */ - public record GroupSpec(int channel, ElementType elementType, boolean isCategorize) { + public record GroupSpec(int channel, ElementType elementType, boolean isCategorize, @Nullable TopNDef topNDef) { public GroupSpec(int channel, ElementType elementType) { - this(channel, elementType, false); + this(channel, elementType, false, null); + } + + public GroupSpec(int channel, ElementType elementType, boolean isCategorize) { + this(channel, elementType, isCategorize, null); } } @@ -134,7 +152,12 @@ public GroupSpec(int channel, ElementType elementType) { */ public static BlockHash build(List groups, BlockFactory blockFactory, int emitBatchSize, boolean allowBrokenOptimizations) { if (groups.size() == 1) { - return newForElementType(groups.get(0).channel(), groups.get(0).elementType(), blockFactory); + GroupSpec group = groups.get(0); + if (group.topNDef() != null && group.elementType() == ElementType.LONG) { + TopNDef topNDef = group.topNDef(); + return new LongTopNBlockHash(group.channel(), topNDef.asc(), topNDef.nullsFirst(), topNDef.limit(), blockFactory); + } + return newForElementType(group.channel(), group.elementType(), blockFactory); } if (groups.stream().allMatch(g -> g.elementType == ElementType.BYTES_REF)) { switch (groups.size()) { diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongTopNBlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongTopNBlockHash.java new file mode 100644 index 0000000000000..58fe427618135 --- /dev/null +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongTopNBlockHash.java @@ -0,0 +1,323 @@ +/* + * 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.compute.aggregation.blockhash; + +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.BitArray; +import org.elasticsearch.common.util.LongHash; +import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; +import org.elasticsearch.compute.data.IntBlock; +import org.elasticsearch.compute.data.IntVector; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.LongVector; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.data.sort.LongTopNSet; +import org.elasticsearch.compute.operator.mvdedupe.MultivalueDedupe; +import org.elasticsearch.compute.operator.mvdedupe.TopNMultivalueDedupeLong; +import org.elasticsearch.core.ReleasableIterator; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.search.sort.SortOrder; + +import java.util.BitSet; + +/** + * Maps a {@link LongBlock} column to group ids, keeping only the top N values. + */ +final class LongTopNBlockHash extends BlockHash { + private final int channel; + private final boolean asc; + private final boolean nullsFirst; + private final int limit; + private final LongHash hash; + private final LongTopNSet topValues; + + /** + * Have we seen any {@code null} values? + *

+ * We reserve the 0 ordinal for the {@code null} key so methods like + * {@link #nonEmpty} need to skip 0 if we haven't seen any null values. + *

+ */ + private boolean hasNull; + + LongTopNBlockHash(int channel, boolean asc, boolean nullsFirst, int limit, BlockFactory blockFactory) { + super(blockFactory); + assert limit > 0 : "LongTopNBlockHash requires a limit greater than 0"; + this.channel = channel; + this.asc = asc; + this.nullsFirst = nullsFirst; + this.limit = limit; + + boolean success = false; + try { + this.hash = new LongHash(1, blockFactory.bigArrays()); + this.topValues = new LongTopNSet(blockFactory.bigArrays(), asc ? SortOrder.ASC : SortOrder.DESC, limit); + success = true; + } finally { + if (success == false) { + close(); + } + } + + } + + @Override + public void add(Page page, GroupingAggregatorFunction.AddInput addInput) { + // TODO track raw counts and which implementation we pick for the profiler - #114008 + var block = page.getBlock(channel); + + if (block.areAllValuesNull() && acceptNull()) { + hasNull = true; + try (IntVector groupIds = blockFactory.newConstantIntVector(0, block.getPositionCount())) { + addInput.add(0, groupIds); + } + return; + } + LongBlock castBlock = (LongBlock) block; + LongVector vector = castBlock.asVector(); + if (vector == null) { + try (IntBlock groupIds = add(castBlock)) { + addInput.add(0, groupIds); + } + return; + } + try (IntBlock groupIds = add(vector)) { + addInput.add(0, groupIds); + } + } + + /** + * Tries to add null to the top values, and returns true if it was successful. + */ + private boolean acceptNull() { + if (hasNull) { + return true; + } + + if (nullsFirst) { + hasNull = true; + // Reduce the limit of the sort by one, as it's not filled with a null + assert topValues.getLimit() == limit : "The top values can't be reduced twice"; + topValues.reduceLimitByOne(); + return true; + } + + if (topValues.getCount() < limit) { + hasNull = true; + return true; + } + + return false; + } + + /** + * Tries to add the value to the top values, and returns true if it was successful. + */ + private boolean acceptValue(long value) { + if (topValues.collect(value) == false) { + return false; + } + + // Full top and null, there's an extra value/null we must remove + if (topValues.getCount() == limit && hasNull && nullsFirst == false) { + hasNull = false; + } + + return true; + } + + /** + * Returns true if the value is in, or can be added to the top; false otherwise. + */ + private boolean isAcceptable(long value) { + return isTopComplete() == false || (hasNull && nullsFirst == false) || isInTop(value); + } + + /** + * Returns true if the value is in the top; false otherwise. + *

+ * This method does not check if the value is currently part of the top; only if it's better or equal than the current worst value. + *

+ */ + private boolean isInTop(long value) { + return asc ? value <= topValues.getWorstValue() : value >= topValues.getWorstValue(); + } + + /** + * Returns true if there are {@code limit} values in the blockhash; false otherwise. + */ + private boolean isTopComplete() { + return topValues.getCount() >= limit - (hasNull ? 1 : 0); + } + + /** + * Adds the vector values to the hash, and returns a new vector with the group IDs for those positions. + */ + IntBlock add(LongVector vector) { + int positions = vector.getPositionCount(); + + // Add all values to the top set, so we don't end up sending invalid values later + for (int i = 0; i < positions; i++) { + long v = vector.getLong(i); + acceptValue(v); + } + + // Create a block with the groups + try (var builder = blockFactory.newIntBlockBuilder(positions)) { + for (int i = 0; i < positions; i++) { + long v = vector.getLong(i); + if (isAcceptable(v)) { + builder.appendInt(Math.toIntExact(hashOrdToGroupNullReserved(hash.add(v)))); + } else { + builder.appendNull(); + } + } + return builder.build(); + } + } + + /** + * Adds the block values to the hash, and returns a new vector with the group IDs for those positions. + *

+ * For nulls, a 0 group ID is used. For multivalues, a multivalue is used with all the group IDs. + *

+ */ + IntBlock add(LongBlock block) { + // Add all the values to the top set, so we don't end up sending invalid values later + for (int p = 0; p < block.getPositionCount(); p++) { + int count = block.getValueCount(p); + if (count == 0) { + acceptNull(); + continue; + } + int first = block.getFirstValueIndex(p); + + for (int i = 0; i < count; i++) { + long value = block.getLong(first + i); + acceptValue(value); + } + } + + // TODO: Make the custom dedupe *less custom* + MultivalueDedupe.HashResult result = new TopNMultivalueDedupeLong(block, hasNull, this::isAcceptable).hashAdd(blockFactory, hash); + + return result.ords(); + } + + @Override + public ReleasableIterator lookup(Page page, ByteSizeValue targetBlockSize) { + var block = page.getBlock(channel); + if (block.areAllValuesNull()) { + return ReleasableIterator.single(blockFactory.newConstantIntVector(0, block.getPositionCount()).asBlock()); + } + + LongBlock castBlock = (LongBlock) block; + LongVector vector = castBlock.asVector(); + // TODO honor targetBlockSize and chunk the pages if requested. + if (vector == null) { + return ReleasableIterator.single(lookup(castBlock)); + } + return ReleasableIterator.single(lookup(vector)); + } + + private IntBlock lookup(LongVector vector) { + int positions = vector.getPositionCount(); + try (var builder = blockFactory.newIntBlockBuilder(positions)) { + for (int i = 0; i < positions; i++) { + long v = vector.getLong(i); + long found = hash.find(v); + if (found < 0 || isAcceptable(v) == false) { + builder.appendNull(); + } else { + builder.appendInt(Math.toIntExact(hashOrdToGroupNullReserved(found))); + } + } + return builder.build(); + } + } + + private IntBlock lookup(LongBlock block) { + return new TopNMultivalueDedupeLong(block, hasNull, this::isAcceptable).hashLookup(blockFactory, hash); + } + + @Override + public LongBlock[] getKeys() { + if (hasNull) { + final long[] keys = new long[topValues.getCount() + 1]; + int keysIndex = 1; + for (int i = 1; i < hash.size() + 1; i++) { + long value = hash.get(i - 1); + if (isInTop(value)) { + keys[keysIndex++] = value; + } + } + BitSet nulls = new BitSet(1); + nulls.set(0); + return new LongBlock[] { + blockFactory.newLongArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING) }; + } + final long[] keys = new long[topValues.getCount()]; + int keysIndex = 0; + for (int i = 0; i < hash.size(); i++) { + long value = hash.get(i); + if (isInTop(value)) { + keys[keysIndex++] = value; + } + } + return new LongBlock[] { blockFactory.newLongArrayVector(keys, keys.length).asBlock() }; + } + + @Override + public IntVector nonEmpty() { + int nullOffset = hasNull ? 1 : 0; + final int[] ids = new int[topValues.getCount() + nullOffset]; + int idsIndex = nullOffset; + for (int i = 1; i < hash.size() + 1; i++) { + long value = hash.get(i - 1); + if (isInTop(value)) { + ids[idsIndex++] = i; + } + } + return blockFactory.newIntArrayVector(ids, ids.length); + } + + @Override + public BitArray seenGroupIds(BigArrays bigArrays) { + BitArray seenGroups = new BitArray(1, bigArrays); + if (hasNull) { + seenGroups.set(0); + } + for (int i = 1; i < hash.size() + 1; i++) { + long value = hash.get(i - 1); + if (isInTop(value)) { + seenGroups.set(i); + } + } + return seenGroups; + } + + @Override + public void close() { + Releasables.close(hash, topValues); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("LongTopNBlockHash{channel=").append(channel); + b.append(", asc=").append(asc); + b.append(", nullsFirst=").append(nullsFirst); + b.append(", limit=").append(limit); + b.append(", entries=").append(hash.size()); + b.append(", hasNull=").append(hasNull); + return b.append('}').toString(); + } +} diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/X-BlockHash.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/X-BlockHash.java.st index d87ff9ba66442..8d7d3fd048d0f 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/X-BlockHash.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/X-BlockHash.java.st @@ -104,6 +104,9 @@ final class $Type$BlockHash extends BlockHash { } } + /** + * Adds the vector values to the hash, and returns a new vector with the group IDs for those positions. + */ IntVector add($Type$Vector vector) { $if(BytesRef)$ var ordinals = vector.asOrdinals(); @@ -128,6 +131,12 @@ $endif$ } } + /** + * Adds the block values to the hash, and returns a new vector with the group IDs for those positions. + *

+ * For nulls, a 0 group ID is used. For multivalues, a multivalue is used with all the group IDs. + *

+ */ IntBlock add($Type$Block block) { $if(BytesRef)$ var ordinals = block.asOrdinals(); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/sort/LongTopNSet.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/sort/LongTopNSet.java new file mode 100644 index 0000000000000..dda4a20db9db6 --- /dev/null +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/sort/LongTopNSet.java @@ -0,0 +1,173 @@ +/* + * 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.compute.data.sort; + +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.BinarySearcher; +import org.elasticsearch.common.util.LongArray; +import org.elasticsearch.core.Releasable; +import org.elasticsearch.core.Releasables; +import org.elasticsearch.search.sort.SortOrder; + +/** + * Aggregates the top N collected values, and keeps them sorted. + *

+ * Collection is O(1) for values out of the current top N. For values better than the worst value, it's O(log(n)). + *

+ */ +public class LongTopNSet implements Releasable { + + private final SortOrder order; + private int limit; + + private final LongArray values; + private final LongBinarySearcher searcher; + + private int count; + + public LongTopNSet(BigArrays bigArrays, SortOrder order, int limit) { + this.order = order; + this.limit = limit; + this.count = 0; + this.values = bigArrays.newLongArray(limit, false); + this.searcher = new LongBinarySearcher(values, order); + } + + /** + * Adds the value to the top N, as long as it is "better" than the worst value, or the top isn't full yet. + */ + public boolean collect(long value) { + if (limit == 0) { + return false; + } + + // Short-circuit if the value is worse than the worst value on the top. + // This avoids a O(log(n)) check in the binary search + if (count == limit && betterThan(getWorstValue(), value)) { + return false; + } + + if (count == 0) { + values.set(0, value); + count++; + return true; + } + + int insertionIndex = this.searcher.search(0, count - 1, value); + + if (insertionIndex == count - 1) { + if (betterThan(getWorstValue(), value)) { + values.set(count, value); + count++; + return true; + } + } + + if (values.get(insertionIndex) == value) { + // Only unique values are stored here + return true; + } + + // The searcher returns the upper bound, so we move right the elements from there + for (int i = Math.min(count, limit - 1); i > insertionIndex; i--) { + values.set(i, values.get(i - 1)); + } + + values.set(insertionIndex, value); + count = Math.min(count + 1, limit); + + return true; + } + + /** + * Reduces the limit of the top N by 1. + *

+ * This method is specifically used to count for the null value, and ignore the extra element here without extra cost. + *

+ */ + public void reduceLimitByOne() { + limit--; + count = Math.min(count, limit); + } + + /** + * Returns the worst value in the top. + *

+ * The worst is the greatest value for {@link SortOrder#ASC}, and the lowest value for {@link SortOrder#DESC}. + *

+ */ + public long getWorstValue() { + assert count > 0; + return values.get(count - 1); + } + + /** + * The order of the sort. + */ + public SortOrder getOrder() { + return order; + } + + public int getLimit() { + return limit; + } + + public int getCount() { + return count; + } + + private static class LongBinarySearcher extends BinarySearcher { + + final LongArray array; + final SortOrder order; + long searchFor; + + LongBinarySearcher(LongArray array, SortOrder order) { + this.array = array; + this.order = order; + this.searchFor = Integer.MIN_VALUE; + } + + @Override + protected int compare(int index) { + // Prevent use of BinarySearcher.search() and force the use of DoubleBinarySearcher.search() + assert this.searchFor != Integer.MIN_VALUE; + + return order.reverseMul() * Long.compare(array.get(index), searchFor); + } + + @Override + protected int getClosestIndex(int index1, int index2) { + // Overridden to always return the upper bound + return Math.max(index1, index2); + } + + @Override + protected double distance(int index) { + throw new UnsupportedOperationException("getClosestIndex() is overridden and doesn't depend on this"); + } + + public int search(int from, int to, long searchFor) { + this.searchFor = searchFor; + return super.search(from, to); + } + } + + /** + * {@code true} if {@code lhs} is "better" than {@code rhs}. + * "Better" in this means "lower" for {@link SortOrder#ASC} and "higher" for {@link SortOrder#DESC}. + */ + private boolean betterThan(long lhs, long rhs) { + return getOrder().reverseMul() * Long.compare(lhs, rhs) < 0; + } + + @Override + public final void close() { + Releasables.close(values); + } +} diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/mvdedupe/TopNMultivalueDedupeLong.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/mvdedupe/TopNMultivalueDedupeLong.java new file mode 100644 index 0000000000000..40cd876b429b7 --- /dev/null +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/mvdedupe/TopNMultivalueDedupeLong.java @@ -0,0 +1,414 @@ +/* + * 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.compute.operator.mvdedupe; + +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.common.util.LongHash; +import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction; +import org.elasticsearch.compute.aggregation.blockhash.BlockHash; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; +import org.elasticsearch.compute.data.IntBlock; +import org.elasticsearch.compute.data.LongBlock; + +import java.util.Arrays; +import java.util.function.Predicate; + +/** + * Removes duplicate values from multivalued positions, and keeps only the ones that pass the filters. + *

+ * Clone of {@link MultivalueDedupeLong}, but for it accepts a predicate and nulls flag to filter the values. + *

+ */ +public class TopNMultivalueDedupeLong { + /** + * The number of entries before we switch from and {@code n^2} strategy + * with low overhead to an {@code n*log(n)} strategy with higher overhead. + * The choice of number has been experimentally derived. + */ + static final int ALWAYS_COPY_MISSING = 300; + + /** + * The {@link Block} being deduplicated. + */ + final LongBlock block; + /** + * Whether the hash expects nulls or not. + */ + final boolean acceptNulls; + /** + * A predicate to test if a value is part of the top N or not. + */ + final Predicate isAcceptable; + /** + * Oversized array of values that contains deduplicated values after + * running {@link #copyMissing} and sorted values after calling + * {@link #copyAndSort} + */ + long[] work = new long[ArrayUtil.oversize(2, Long.BYTES)]; + /** + * After calling {@link #copyMissing} or {@link #copyAndSort} this is + * the number of values in {@link #work} for the current position. + */ + int w; + + public TopNMultivalueDedupeLong(LongBlock block, boolean acceptNulls, Predicate isAcceptable) { + this.block = block; + this.acceptNulls = acceptNulls; + this.isAcceptable = isAcceptable; + } + + /** + * Dedupe values, add them to the hash, and build an {@link IntBlock} of + * their hashes. This block is suitable for passing as the grouping block + * to a {@link GroupingAggregatorFunction}. + */ + public MultivalueDedupe.HashResult hashAdd(BlockFactory blockFactory, LongHash hash) { + try (IntBlock.Builder builder = blockFactory.newIntBlockBuilder(block.getPositionCount())) { + boolean sawNull = false; + for (int p = 0; p < block.getPositionCount(); p++) { + int count = block.getValueCount(p); + int first = block.getFirstValueIndex(p); + switch (count) { + case 0 -> { + if (acceptNulls) { + sawNull = true; + builder.appendInt(0); + } else { + builder.appendNull(); + } + } + case 1 -> { + long v = block.getLong(first); + hashAdd(builder, hash, v); + } + default -> { + if (count < ALWAYS_COPY_MISSING) { + copyMissing(first, count); + hashAddUniquedWork(hash, builder); + } else { + copyAndSort(first, count); + hashAddSortedWork(hash, builder); + } + } + } + } + return new MultivalueDedupe.HashResult(builder.build(), sawNull); + } + } + + /** + * Dedupe values and build an {@link IntBlock} of their hashes. This block is + * suitable for passing as the grouping block to a {@link GroupingAggregatorFunction}. + */ + public IntBlock hashLookup(BlockFactory blockFactory, LongHash hash) { + try (IntBlock.Builder builder = blockFactory.newIntBlockBuilder(block.getPositionCount())) { + for (int p = 0; p < block.getPositionCount(); p++) { + int count = block.getValueCount(p); + int first = block.getFirstValueIndex(p); + switch (count) { + case 0 -> { + if (acceptNulls) { + builder.appendInt(0); + } else { + builder.appendNull(); + } + } + case 1 -> { + long v = block.getLong(first); + hashLookupSingle(builder, hash, v); + } + default -> { + if (count < ALWAYS_COPY_MISSING) { + copyMissing(first, count); + hashLookupUniquedWork(hash, builder); + } else { + copyAndSort(first, count); + hashLookupSortedWork(hash, builder); + } + } + } + } + return builder.build(); + } + } + + /** + * Copy all value from the position into {@link #work} and then + * sorts it {@code n * log(n)}. + */ + void copyAndSort(int first, int count) { + grow(count); + int end = first + count; + + w = 0; + for (int i = first; i < end; i++) { + long value = block.getLong(i); + if (isAcceptable.test(value)) { + work[w++] = value; + } + } + + Arrays.sort(work, 0, w); + } + + /** + * Fill {@link #work} with the unique values in the position by scanning + * all fields already copied {@code n^2}. + */ + void copyMissing(int first, int count) { + grow(count); + int end = first + count; + + // Find the first acceptable value + for (; first < end; first++) { + long v = block.getLong(first); + if (isAcceptable.test(v)) { + break; + } + } + if (first == end) { + w = 0; + return; + } + + work[0] = block.getLong(first); + w = 1; + i: for (int i = first + 1; i < end; i++) { + long v = block.getLong(i); + if (isAcceptable.test(v)) { + for (int j = 0; j < w; j++) { + if (v == work[j]) { + continue i; + } + } + work[w++] = v; + } + } + } + + /** + * Writes an already deduplicated {@link #work} to a hash. + */ + private void hashAddUniquedWork(LongHash hash, IntBlock.Builder builder) { + if (w == 0) { + builder.appendNull(); + return; + } + + if (w == 1) { + hashAddNoCheck(builder, hash, work[0]); + return; + } + + builder.beginPositionEntry(); + for (int i = 0; i < w; i++) { + hashAddNoCheck(builder, hash, work[i]); + } + builder.endPositionEntry(); + } + + /** + * Writes a sorted {@link #work} to a hash, skipping duplicates. + */ + private void hashAddSortedWork(LongHash hash, IntBlock.Builder builder) { + if (w == 0) { + builder.appendNull(); + return; + } + + if (w == 1) { + hashAddNoCheck(builder, hash, work[0]); + return; + } + + builder.beginPositionEntry(); + long prev = work[0]; + hashAddNoCheck(builder, hash, prev); + for (int i = 1; i < w; i++) { + if (false == valuesEqual(prev, work[i])) { + prev = work[i]; + hashAddNoCheck(builder, hash, prev); + } + } + builder.endPositionEntry(); + } + + /** + * Looks up an already deduplicated {@link #work} to a hash. + */ + private void hashLookupUniquedWork(LongHash hash, IntBlock.Builder builder) { + if (w == 0) { + builder.appendNull(); + return; + } + if (w == 1) { + hashLookupSingle(builder, hash, work[0]); + return; + } + + int i = 1; + long firstLookup = hashLookup(hash, work[0]); + while (firstLookup < 0) { + if (i >= w) { + // Didn't find any values + builder.appendNull(); + return; + } + firstLookup = hashLookup(hash, work[i]); + i++; + } + + /* + * Step 2 - find the next unique value in the hash + */ + boolean foundSecond = false; + while (i < w) { + long nextLookup = hashLookup(hash, work[i]); + if (nextLookup >= 0) { + builder.beginPositionEntry(); + appendFound(builder, firstLookup); + appendFound(builder, nextLookup); + i++; + foundSecond = true; + break; + } + i++; + } + + /* + * Step 3a - we didn't find a second value, just emit the first one + */ + if (false == foundSecond) { + appendFound(builder, firstLookup); + return; + } + + /* + * Step 3b - we found a second value, search for more + */ + while (i < w) { + long nextLookup = hashLookup(hash, work[i]); + if (nextLookup >= 0) { + appendFound(builder, nextLookup); + } + i++; + } + builder.endPositionEntry(); + } + + /** + * Looks up a sorted {@link #work} to a hash, skipping duplicates. + */ + private void hashLookupSortedWork(LongHash hash, IntBlock.Builder builder) { + if (w == 1) { + hashLookupSingle(builder, hash, work[0]); + return; + } + + /* + * Step 1 - find the first unique value in the hash + * i will contain the next value to probe + * prev will contain the first value in the array contained in the hash + * firstLookup will contain the first value in the hash + */ + int i = 1; + long prev = work[0]; + long firstLookup = hashLookup(hash, prev); + while (firstLookup < 0) { + if (i >= w) { + // Didn't find any values + builder.appendNull(); + return; + } + prev = work[i]; + firstLookup = hashLookup(hash, prev); + i++; + } + + /* + * Step 2 - find the next unique value in the hash + */ + boolean foundSecond = false; + while (i < w) { + if (false == valuesEqual(prev, work[i])) { + long nextLookup = hashLookup(hash, work[i]); + if (nextLookup >= 0) { + prev = work[i]; + builder.beginPositionEntry(); + appendFound(builder, firstLookup); + appendFound(builder, nextLookup); + i++; + foundSecond = true; + break; + } + } + i++; + } + + /* + * Step 3a - we didn't find a second value, just emit the first one + */ + if (false == foundSecond) { + appendFound(builder, firstLookup); + return; + } + + /* + * Step 3b - we found a second value, search for more + */ + while (i < w) { + if (false == valuesEqual(prev, work[i])) { + long nextLookup = hashLookup(hash, work[i]); + if (nextLookup >= 0) { + prev = work[i]; + appendFound(builder, nextLookup); + } + } + i++; + } + builder.endPositionEntry(); + } + + private void grow(int size) { + work = ArrayUtil.grow(work, size); + } + + private void hashAdd(IntBlock.Builder builder, LongHash hash, long v) { + if (isAcceptable.test(v)) { + hashAddNoCheck(builder, hash, v); + } else { + builder.appendNull(); + } + } + + private void hashAddNoCheck(IntBlock.Builder builder, LongHash hash, long v) { + appendFound(builder, hash.add(v)); + } + + private long hashLookup(LongHash hash, long v) { + return isAcceptable.test(v) ? hash.find(v) : -1; + } + + private void hashLookupSingle(IntBlock.Builder builder, LongHash hash, long v) { + long found = hashLookup(hash, v); + if (found >= 0) { + appendFound(builder, found); + } else { + builder.appendNull(); + } + } + + private void appendFound(IntBlock.Builder builder, long found) { + builder.appendInt(Math.toIntExact(BlockHash.hashOrdToGroupNullReserved(found))); + } + + private static boolean valuesEqual(long lhs, long rhs) { + return lhs == rhs; + } +} diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTestCase.java index 5cd9120e05305..8d966a93bb6c2 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTestCase.java @@ -7,15 +7,40 @@ package org.elasticsearch.compute.aggregation.blockhash; +import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.BitArray; import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BooleanBlock; +import org.elasticsearch.compute.data.BytesRefBlock; +import org.elasticsearch.compute.data.DoubleBlock; +import org.elasticsearch.compute.data.IntArrayBlock; +import org.elasticsearch.compute.data.IntBigArrayBlock; +import org.elasticsearch.compute.data.IntBlock; +import org.elasticsearch.compute.data.IntVector; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.test.MockBlockFactory; +import org.elasticsearch.compute.test.TestBlockFactory; +import org.elasticsearch.core.ReleasableIterator; +import org.elasticsearch.core.Releasables; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.test.ESTestCase; +import org.junit.After; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -25,10 +50,187 @@ public abstract class BlockHashTestCase extends ESTestCase { final BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, mockBreakerService(breaker)); final MockBlockFactory blockFactory = new MockBlockFactory(breaker, bigArrays); + @After + public void checkBreaker() { + blockFactory.ensureAllBlocksAreReleased(); + assertThat(breaker.getUsed(), is(0L)); + } + // A breaker service that always returns the given breaker for getBreaker(CircuitBreaker.REQUEST) private static CircuitBreakerService mockBreakerService(CircuitBreaker breaker) { CircuitBreakerService breakerService = mock(CircuitBreakerService.class); when(breakerService.getBreaker(CircuitBreaker.REQUEST)).thenReturn(breaker); return breakerService; } + + protected record OrdsAndKeys(String description, int positionOffset, IntBlock ords, Block[] keys, IntVector nonEmpty) {} + + protected static void hash(boolean collectKeys, BlockHash blockHash, Consumer callback, Block... values) { + blockHash.add(new Page(values), new GroupingAggregatorFunction.AddInput() { + private void addBlock(int positionOffset, IntBlock groupIds) { + OrdsAndKeys result = new OrdsAndKeys( + blockHash.toString(), + positionOffset, + groupIds, + collectKeys ? blockHash.getKeys() : null, + blockHash.nonEmpty() + ); + + try { + Set allowedOrds = new HashSet<>(); + for (int p = 0; p < result.nonEmpty.getPositionCount(); p++) { + allowedOrds.add(result.nonEmpty.getInt(p)); + } + for (int p = 0; p < result.ords.getPositionCount(); p++) { + if (result.ords.isNull(p)) { + continue; + } + int start = result.ords.getFirstValueIndex(p); + int end = start + result.ords.getValueCount(p); + for (int i = start; i < end; i++) { + int ord = result.ords.getInt(i); + if (false == allowedOrds.contains(ord)) { + fail("ord is not allowed " + ord); + } + } + } + callback.accept(result); + } finally { + Releasables.close(result.keys == null ? null : Releasables.wrap(result.keys), result.nonEmpty); + } + } + + @Override + public void add(int positionOffset, IntArrayBlock groupIds) { + addBlock(positionOffset, groupIds); + } + + @Override + public void add(int positionOffset, IntBigArrayBlock groupIds) { + addBlock(positionOffset, groupIds); + } + + @Override + public void add(int positionOffset, IntVector groupIds) { + addBlock(positionOffset, groupIds.asBlock()); + } + + @Override + public void close() { + fail("hashes should not close AddInput"); + } + }); + if (blockHash instanceof LongLongBlockHash == false + && blockHash instanceof BytesRefLongBlockHash == false + && blockHash instanceof BytesRef2BlockHash == false + && blockHash instanceof BytesRef3BlockHash == false) { + Block[] keys = blockHash.getKeys(); + try (ReleasableIterator lookup = blockHash.lookup(new Page(keys), ByteSizeValue.ofKb(between(1, 100)))) { + while (lookup.hasNext()) { + try (IntBlock ords = lookup.next()) { + for (int p = 0; p < ords.getPositionCount(); p++) { + assertFalse(ords.isNull(p)); + } + } + } + } finally { + Releasables.closeExpectNoException(keys); + } + } + } + + private static void assertSeenGroupIdsAndNonEmpty(BlockHash blockHash) { + try (BitArray seenGroupIds = blockHash.seenGroupIds(BigArrays.NON_RECYCLING_INSTANCE); IntVector nonEmpty = blockHash.nonEmpty()) { + assertThat( + "seenGroupIds cardinality doesn't match with nonEmpty size", + seenGroupIds.cardinality(), + equalTo((long) nonEmpty.getPositionCount()) + ); + + for (int position = 0; position < nonEmpty.getPositionCount(); position++) { + int groupId = nonEmpty.getInt(position); + assertThat("group " + groupId + " from nonEmpty isn't set in seenGroupIds", seenGroupIds.get(groupId), is(true)); + } + } + } + + protected void assertOrds(IntBlock ordsBlock, Integer... expectedOrds) { + assertOrds(ordsBlock, Arrays.stream(expectedOrds).map(l -> l == null ? null : new int[] { l }).toArray(int[][]::new)); + } + + protected void assertOrds(IntBlock ordsBlock, int[]... expectedOrds) { + assertEquals(expectedOrds.length, ordsBlock.getPositionCount()); + for (int p = 0; p < expectedOrds.length; p++) { + int start = ordsBlock.getFirstValueIndex(p); + int count = ordsBlock.getValueCount(p); + if (expectedOrds[p] == null) { + if (false == ordsBlock.isNull(p)) { + StringBuilder error = new StringBuilder(); + error.append(p); + error.append(": expected null but was ["); + for (int i = 0; i < count; i++) { + if (i != 0) { + error.append(", "); + } + error.append(ordsBlock.getInt(start + i)); + } + fail(error.append("]").toString()); + } + continue; + } + assertFalse(p + ": expected not null", ordsBlock.isNull(p)); + int[] actual = new int[count]; + for (int i = 0; i < count; i++) { + actual[i] = ordsBlock.getInt(start + i); + } + assertThat("position " + p, actual, equalTo(expectedOrds[p])); + } + } + + protected void assertKeys(Block[] actualKeys, Object... expectedKeys) { + Object[][] flipped = new Object[expectedKeys.length][]; + for (int r = 0; r < flipped.length; r++) { + flipped[r] = new Object[] { expectedKeys[r] }; + } + assertKeys(actualKeys, flipped); + } + + protected void assertKeys(Block[] actualKeys, Object[][] expectedKeys) { + for (int r = 0; r < expectedKeys.length; r++) { + assertThat(actualKeys, arrayWithSize(expectedKeys[r].length)); + } + for (int c = 0; c < actualKeys.length; c++) { + assertThat("block " + c, actualKeys[c].getPositionCount(), equalTo(expectedKeys.length)); + } + for (int r = 0; r < expectedKeys.length; r++) { + for (int c = 0; c < actualKeys.length; c++) { + if (expectedKeys[r][c] == null) { + assertThat("expected null key", actualKeys[c].isNull(r), equalTo(true)); + continue; + } + assertThat("expected non-null key", actualKeys[c].isNull(r), equalTo(false)); + if (expectedKeys[r][c] instanceof Integer v) { + assertThat(((IntBlock) actualKeys[c]).getInt(r), equalTo(v)); + } else if (expectedKeys[r][c] instanceof Long v) { + assertThat(((LongBlock) actualKeys[c]).getLong(r), equalTo(v)); + } else if (expectedKeys[r][c] instanceof Double v) { + assertThat(((DoubleBlock) actualKeys[c]).getDouble(r), equalTo(v)); + } else if (expectedKeys[r][c] instanceof String v) { + assertThat(((BytesRefBlock) actualKeys[c]).getBytesRef(r, new BytesRef()), equalTo(new BytesRef(v))); + } else if (expectedKeys[r][c] instanceof Boolean v) { + assertThat(((BooleanBlock) actualKeys[c]).getBoolean(r), equalTo(v)); + } else { + throw new IllegalArgumentException("unsupported type " + expectedKeys[r][c].getClass()); + } + } + } + } + + protected IntVector intRange(int startInclusive, int endExclusive) { + return IntVector.range(startInclusive, endExclusive, TestBlockFactory.getNonBreakingInstance()); + } + + protected IntVector intVector(int... values) { + return TestBlockFactory.getNonBreakingInstance().newIntArrayVector(values, values.length); + } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTests.java index 34cd299811470..3ff3be5086ad4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/BlockHashTests.java @@ -33,23 +33,17 @@ import org.elasticsearch.core.ReleasableIterator; import org.elasticsearch.core.Releasables; import org.elasticsearch.xpack.esql.core.util.Holder; -import org.junit.After; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Set; import java.util.function.Consumer; import java.util.stream.IntStream; import java.util.stream.LongStream; -import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; public class BlockHashTests extends BlockHashTestCase { @@ -62,12 +56,6 @@ public static List params() { return params; } - @After - public void checkBreaker() { - blockFactory.ensureAllBlocksAreReleased(); - assertThat(breaker.getUsed(), is(0L)); - } - private final boolean forcePackedHash; public BlockHashTests(@Name("forcePackedHash") boolean forcePackedHash) { @@ -78,15 +66,15 @@ public void testIntHash() { int[] values = new int[] { 1, 2, 3, 1, 2, 3, 1, 2, 3 }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:INT], entries=3, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 0, 1, 2, 0, 1, 2); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:INT], entries=3, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 0, 1, 2, 0, 1, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); } else { - assertThat(ordsAndKeys.description, equalTo("IntBlockHash{channel=0, entries=3, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 1, 2, 3, 1, 2, 3, 1, 2, 3); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 4))); + assertThat(ordsAndKeys.description(), equalTo("IntBlockHash{channel=0, entries=3, seenNull=false}")); + assertOrds(ordsAndKeys.ords(), 1, 2, 3, 1, 2, 3, 1, 2, 3); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 4))); } - assertKeys(ordsAndKeys.keys, 1, 2, 3); + assertKeys(ordsAndKeys.keys(), 1, 2, 3); }, blockFactory.newIntArrayVector(values, values.length).asBlock()); } @@ -99,15 +87,15 @@ public void testIntHashWithNulls() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:INT], entries=3, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 1); - assertKeys(ordsAndKeys.keys, 0, null, 2); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:INT], entries=3, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 1); + assertKeys(ordsAndKeys.keys(), 0, null, 2); } else { - assertThat(ordsAndKeys.description, equalTo("IntBlockHash{channel=0, entries=2, seenNull=true}")); - assertOrds(ordsAndKeys.ords, 1, 0, 2, 0); - assertKeys(ordsAndKeys.keys, null, 0, 2); + assertThat(ordsAndKeys.description(), equalTo("IntBlockHash{channel=0, entries=2, seenNull=true}")); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertKeys(ordsAndKeys.keys(), null, 0, 2); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, builder); } } @@ -136,9 +124,9 @@ public void testIntHashWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:INT], entries=4, size=")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:INT], entries=4, size=")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0 }, new int[] { 0, 1 }, new int[] { 2, 0 }, @@ -146,11 +134,11 @@ public void testIntHashWithMultiValuedFields() { new int[] { 3 }, new int[] { 2, 1, 0 } ); - assertKeys(ordsAndKeys.keys, 1, 2, 3, null); + assertKeys(ordsAndKeys.keys(), 1, 2, 3, null); } else { - assertThat(ordsAndKeys.description, equalTo("IntBlockHash{channel=0, entries=3, seenNull=true}")); + assertThat(ordsAndKeys.description(), equalTo("IntBlockHash{channel=0, entries=3, seenNull=true}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 1 }, new int[] { 1, 2 }, new int[] { 3, 1 }, @@ -158,9 +146,9 @@ public void testIntHashWithMultiValuedFields() { new int[] { 0 }, new int[] { 3, 2, 1 } ); - assertKeys(ordsAndKeys.keys, null, 1, 2, 3); + assertKeys(ordsAndKeys.keys(), null, 1, 2, 3); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, builder); } } @@ -170,15 +158,15 @@ public void testLongHash() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG], entries=4, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 0, 2, 1, 3, 2); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG], entries=4, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 0, 2, 1, 3, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); } else { - assertThat(ordsAndKeys.description, equalTo("LongBlockHash{channel=0, entries=4, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 1, 2, 3, 1, 3, 2, 4, 3); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 5))); + assertThat(ordsAndKeys.description(), equalTo("LongBlockHash{channel=0, entries=4, seenNull=false}")); + assertOrds(ordsAndKeys.ords(), 1, 2, 3, 1, 3, 2, 4, 3); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 5))); } - assertKeys(ordsAndKeys.keys, 2L, 1L, 4L, 3L); + assertKeys(ordsAndKeys.keys(), 2L, 1L, 4L, 3L); }, blockFactory.newLongArrayVector(values, values.length).asBlock()); } @@ -191,15 +179,15 @@ public void testLongHashWithNulls() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG], entries=3, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 1); - assertKeys(ordsAndKeys.keys, 0L, null, 2L); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG], entries=3, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 1); + assertKeys(ordsAndKeys.keys(), 0L, null, 2L); } else { - assertThat(ordsAndKeys.description, equalTo("LongBlockHash{channel=0, entries=2, seenNull=true}")); - assertOrds(ordsAndKeys.ords, 1, 0, 2, 0); - assertKeys(ordsAndKeys.keys, null, 0L, 2L); + assertThat(ordsAndKeys.description(), equalTo("LongBlockHash{channel=0, entries=2, seenNull=true}")); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertKeys(ordsAndKeys.keys(), null, 0L, 2L); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, builder); } } @@ -228,9 +216,9 @@ public void testLongHashWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG], entries=4, size=")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG], entries=4, size=")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0 }, new int[] { 0, 1, 2 }, new int[] { 0 }, @@ -238,11 +226,11 @@ public void testLongHashWithMultiValuedFields() { new int[] { 3 }, new int[] { 2, 1, 0 } ); - assertKeys(ordsAndKeys.keys, 1L, 2L, 3L, null); + assertKeys(ordsAndKeys.keys(), 1L, 2L, 3L, null); } else { - assertThat(ordsAndKeys.description, equalTo("LongBlockHash{channel=0, entries=3, seenNull=true}")); + assertThat(ordsAndKeys.description(), equalTo("LongBlockHash{channel=0, entries=3, seenNull=true}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 1 }, new int[] { 1, 2, 3 }, new int[] { 1 }, @@ -250,9 +238,9 @@ public void testLongHashWithMultiValuedFields() { new int[] { 0 }, new int[] { 3, 2, 1 } ); - assertKeys(ordsAndKeys.keys, null, 1L, 2L, 3L); + assertKeys(ordsAndKeys.keys(), null, 1L, 2L, 3L); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, builder); } } @@ -261,15 +249,15 @@ public void testDoubleHash() { double[] values = new double[] { 2.0, 1.0, 4.0, 2.0, 4.0, 1.0, 3.0, 4.0 }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:DOUBLE], entries=4, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 0, 2, 1, 3, 2); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:DOUBLE], entries=4, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 0, 2, 1, 3, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); } else { - assertThat(ordsAndKeys.description, equalTo("DoubleBlockHash{channel=0, entries=4, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 1, 2, 3, 1, 3, 2, 4, 3); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 5))); + assertThat(ordsAndKeys.description(), equalTo("DoubleBlockHash{channel=0, entries=4, seenNull=false}")); + assertOrds(ordsAndKeys.ords(), 1, 2, 3, 1, 3, 2, 4, 3); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 5))); } - assertKeys(ordsAndKeys.keys, 2.0, 1.0, 4.0, 3.0); + assertKeys(ordsAndKeys.keys(), 2.0, 1.0, 4.0, 3.0); }, blockFactory.newDoubleArrayVector(values, values.length).asBlock()); } @@ -282,15 +270,15 @@ public void testDoubleHashWithNulls() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:DOUBLE], entries=3, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 1); - assertKeys(ordsAndKeys.keys, 0.0, null, 2.0); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:DOUBLE], entries=3, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 1); + assertKeys(ordsAndKeys.keys(), 0.0, null, 2.0); } else { - assertThat(ordsAndKeys.description, equalTo("DoubleBlockHash{channel=0, entries=2, seenNull=true}")); - assertOrds(ordsAndKeys.ords, 1, 0, 2, 0); - assertKeys(ordsAndKeys.keys, null, 0.0, 2.0); + assertThat(ordsAndKeys.description(), equalTo("DoubleBlockHash{channel=0, entries=2, seenNull=true}")); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertKeys(ordsAndKeys.keys(), null, 0.0, 2.0); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, builder); } } @@ -318,9 +306,9 @@ public void testDoubleHashWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:DOUBLE], entries=4, size=")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:DOUBLE], entries=4, size=")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0 }, new int[] { 1, 2 }, new int[] { 2, 1 }, @@ -328,11 +316,11 @@ public void testDoubleHashWithMultiValuedFields() { new int[] { 3 }, new int[] { 0, 1 } ); - assertKeys(ordsAndKeys.keys, 1.0, 2.0, 3.0, null); + assertKeys(ordsAndKeys.keys(), 1.0, 2.0, 3.0, null); } else { - assertThat(ordsAndKeys.description, equalTo("DoubleBlockHash{channel=0, entries=3, seenNull=true}")); + assertThat(ordsAndKeys.description(), equalTo("DoubleBlockHash{channel=0, entries=3, seenNull=true}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 1 }, new int[] { 2, 3 }, new int[] { 3, 2 }, @@ -340,9 +328,9 @@ public void testDoubleHashWithMultiValuedFields() { new int[] { 0 }, new int[] { 1, 2 } ); - assertKeys(ordsAndKeys.keys, null, 1.0, 2.0, 3.0); + assertKeys(ordsAndKeys.keys(), null, 1.0, 2.0, 3.0); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, builder); } } @@ -360,17 +348,17 @@ public void testBasicBytesRefHash() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 0, 2, 1, 3, 2); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 0, 2, 1, 3, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); } else { - assertThat(ordsAndKeys.description, startsWith("BytesRefBlockHash{channel=0, entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 1, 2, 3, 1, 3, 2, 4, 3); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 5))); + assertThat(ordsAndKeys.description(), startsWith("BytesRefBlockHash{channel=0, entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b, seenNull=false}")); + assertOrds(ordsAndKeys.ords(), 1, 2, 3, 1, 3, 2, 4, 3); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 5))); } - assertKeys(ordsAndKeys.keys, "item-2", "item-1", "item-4", "item-3"); + assertKeys(ordsAndKeys.keys(), "item-2", "item-1", "item-4", "item-3"); }, builder); } } @@ -384,17 +372,17 @@ public void testBytesRefHashWithNulls() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=3, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 1); - assertKeys(ordsAndKeys.keys, "cat", null, "dog"); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=3, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 1); + assertKeys(ordsAndKeys.keys(), "cat", null, "dog"); } else { - assertThat(ordsAndKeys.description, startsWith("BytesRefBlockHash{channel=0, entries=2, size=")); - assertThat(ordsAndKeys.description, endsWith("b, seenNull=true}")); - assertOrds(ordsAndKeys.ords, 1, 0, 2, 0); - assertKeys(ordsAndKeys.keys, null, "cat", "dog"); + assertThat(ordsAndKeys.description(), startsWith("BytesRefBlockHash{channel=0, entries=2, size=")); + assertThat(ordsAndKeys.description(), endsWith("b, seenNull=true}")); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertKeys(ordsAndKeys.keys(), null, "cat", "dog"); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, builder); } } @@ -423,10 +411,10 @@ public void testBytesRefHashWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0 }, new int[] { 0, 1 }, new int[] { 1, 2 }, @@ -434,12 +422,12 @@ public void testBytesRefHashWithMultiValuedFields() { new int[] { 3 }, new int[] { 2, 1 } ); - assertKeys(ordsAndKeys.keys, "foo", "bar", "bort", null); + assertKeys(ordsAndKeys.keys(), "foo", "bar", "bort", null); } else { - assertThat(ordsAndKeys.description, startsWith("BytesRefBlockHash{channel=0, entries=3, size=")); - assertThat(ordsAndKeys.description, endsWith("b, seenNull=true}")); + assertThat(ordsAndKeys.description(), startsWith("BytesRefBlockHash{channel=0, entries=3, size=")); + assertThat(ordsAndKeys.description(), endsWith("b, seenNull=true}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 1 }, new int[] { 1, 2 }, new int[] { 2, 3 }, @@ -447,9 +435,9 @@ public void testBytesRefHashWithMultiValuedFields() { new int[] { 0 }, new int[] { 3, 2 } ); - assertKeys(ordsAndKeys.keys, null, "foo", "bar", "bort"); + assertKeys(ordsAndKeys.keys(), null, "foo", "bar", "bort"); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, builder); } } @@ -474,17 +462,17 @@ public void testBasicOrdinals() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 0, 2, 1, 3, 2); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); - assertKeys(ordsAndKeys.keys, "item-2", "item-1", "item-4", "item-3"); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 0, 2, 1, 3, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); + assertKeys(ordsAndKeys.keys(), "item-2", "item-1", "item-4", "item-3"); } else { - assertThat(ordsAndKeys.description, startsWith("BytesRefBlockHash{channel=0, entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 2, 1, 4, 2, 4, 1, 3, 4); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 5))); - assertKeys(ordsAndKeys.keys, "item-1", "item-2", "item-3", "item-4"); + assertThat(ordsAndKeys.description(), startsWith("BytesRefBlockHash{channel=0, entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b, seenNull=false}")); + assertOrds(ordsAndKeys.ords(), 2, 1, 4, 2, 4, 1, 3, 4); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 5))); + assertKeys(ordsAndKeys.keys(), "item-1", "item-2", "item-3", "item-4"); } }, new OrdinalBytesRefVector(ords.build(), bytes.build()).asBlock()); } @@ -504,17 +492,17 @@ public void testOrdinalsWithNulls() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=3, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 1); - assertKeys(ordsAndKeys.keys, "cat", null, "dog"); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=3, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 1); + assertKeys(ordsAndKeys.keys(), "cat", null, "dog"); } else { - assertThat(ordsAndKeys.description, startsWith("BytesRefBlockHash{channel=0, entries=2, size=")); - assertThat(ordsAndKeys.description, endsWith("b, seenNull=true}")); - assertOrds(ordsAndKeys.ords, 1, 0, 2, 0); - assertKeys(ordsAndKeys.keys, null, "cat", "dog"); + assertThat(ordsAndKeys.description(), startsWith("BytesRefBlockHash{channel=0, entries=2, size=")); + assertThat(ordsAndKeys.description(), endsWith("b, seenNull=true}")); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertKeys(ordsAndKeys.keys(), null, "cat", "dog"); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, new OrdinalBytesRefBlock(ords.build(), bytes.build())); } } @@ -550,10 +538,10 @@ public void testOrdinalsWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BYTES_REF], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0 }, new int[] { 0, 1 }, new int[] { 1, 2 }, @@ -561,12 +549,12 @@ public void testOrdinalsWithMultiValuedFields() { new int[] { 3 }, new int[] { 2, 1 } ); - assertKeys(ordsAndKeys.keys, "foo", "bar", "bort", null); + assertKeys(ordsAndKeys.keys(), "foo", "bar", "bort", null); } else { - assertThat(ordsAndKeys.description, startsWith("BytesRefBlockHash{channel=0, entries=3, size=")); - assertThat(ordsAndKeys.description, endsWith("b, seenNull=true}")); + assertThat(ordsAndKeys.description(), startsWith("BytesRefBlockHash{channel=0, entries=3, size=")); + assertThat(ordsAndKeys.description(), endsWith("b, seenNull=true}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 1 }, new int[] { 1, 2 }, new int[] { 2, 3 }, @@ -574,9 +562,9 @@ public void testOrdinalsWithMultiValuedFields() { new int[] { 0 }, new int[] { 3, 2 } ); - assertKeys(ordsAndKeys.keys, null, "foo", "bar", "bort"); + assertKeys(ordsAndKeys.keys(), null, "foo", "bar", "bort"); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, new OrdinalBytesRefBlock(ords.build(), bytes.build())); } } @@ -585,15 +573,18 @@ public void testBooleanHashFalseFirst() { boolean[] values = new boolean[] { false, true, true, true, true }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=2, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 1, 1, 1); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 2))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=2, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 1, 1, 1); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 2))); } else { - assertThat(ordsAndKeys.description, equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=true, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 1, 2, 2, 2, 2); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 3))); + assertThat( + ordsAndKeys.description(), + equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=true, seenNull=false}") + ); + assertOrds(ordsAndKeys.ords(), 1, 2, 2, 2, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 3))); } - assertKeys(ordsAndKeys.keys, false, true); + assertKeys(ordsAndKeys.keys(), false, true); }, blockFactory.newBooleanArrayVector(values, values.length).asBlock()); } @@ -601,15 +592,18 @@ public void testBooleanHashTrueFirst() { boolean[] values = new boolean[] { true, false, false, true, true }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=2, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 1, 0, 0); - assertKeys(ordsAndKeys.keys, true, false); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 2))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=2, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 1, 0, 0); + assertKeys(ordsAndKeys.keys(), true, false); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 2))); } else { - assertThat(ordsAndKeys.description, equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=true, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 2, 1, 1, 2, 2); - assertKeys(ordsAndKeys.keys, false, true); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(1, 3))); + assertThat( + ordsAndKeys.description(), + equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=true, seenNull=false}") + ); + assertOrds(ordsAndKeys.ords(), 2, 1, 1, 2, 2); + assertKeys(ordsAndKeys.keys(), false, true); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 3))); } }, blockFactory.newBooleanArrayVector(values, values.length).asBlock()); } @@ -618,15 +612,18 @@ public void testBooleanHashTrueOnly() { boolean[] values = new boolean[] { true, true, true, true }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=1, size=")); - assertOrds(ordsAndKeys.ords, 0, 0, 0, 0); - assertKeys(ordsAndKeys.keys, true); - assertThat(ordsAndKeys.nonEmpty, equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(0, 1))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=1, size=")); + assertOrds(ordsAndKeys.ords(), 0, 0, 0, 0); + assertKeys(ordsAndKeys.keys(), true); + assertThat(ordsAndKeys.nonEmpty(), equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(0, 1))); } else { - assertThat(ordsAndKeys.description, equalTo("BooleanBlockHash{channel=0, seenFalse=false, seenTrue=true, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 2, 2, 2, 2); - assertKeys(ordsAndKeys.keys, true); - assertThat(ordsAndKeys.nonEmpty, equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(2, 1))); + assertThat( + ordsAndKeys.description(), + equalTo("BooleanBlockHash{channel=0, seenFalse=false, seenTrue=true, seenNull=false}") + ); + assertOrds(ordsAndKeys.ords(), 2, 2, 2, 2); + assertKeys(ordsAndKeys.keys(), true); + assertThat(ordsAndKeys.nonEmpty(), equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(2, 1))); } }, blockFactory.newBooleanArrayVector(values, values.length).asBlock()); } @@ -635,15 +632,18 @@ public void testBooleanHashFalseOnly() { boolean[] values = new boolean[] { false, false, false, false }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=1, size=")); - assertOrds(ordsAndKeys.ords, 0, 0, 0, 0); - assertThat(ordsAndKeys.nonEmpty, equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(0, 1))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=1, size=")); + assertOrds(ordsAndKeys.ords(), 0, 0, 0, 0); + assertThat(ordsAndKeys.nonEmpty(), equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(0, 1))); } else { - assertThat(ordsAndKeys.description, equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=false, seenNull=false}")); - assertOrds(ordsAndKeys.ords, 1, 1, 1, 1); - assertThat(ordsAndKeys.nonEmpty, equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(1, 1))); + assertThat( + ordsAndKeys.description(), + equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=false, seenNull=false}") + ); + assertOrds(ordsAndKeys.ords(), 1, 1, 1, 1); + assertThat(ordsAndKeys.nonEmpty(), equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(1, 1))); } - assertKeys(ordsAndKeys.keys, false); + assertKeys(ordsAndKeys.keys(), false); }, blockFactory.newBooleanArrayVector(values, values.length).asBlock()); } @@ -656,18 +656,18 @@ public void testBooleanHashWithNulls() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=3, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 1); - assertKeys(ordsAndKeys.keys, false, null, true); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=3, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 1); + assertKeys(ordsAndKeys.keys(), false, null, true); } else { assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=true, seenNull=true}") ); - assertOrds(ordsAndKeys.ords, 1, 0, 2, 0); - assertKeys(ordsAndKeys.keys, null, false, true); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertKeys(ordsAndKeys.keys(), null, false, true); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, builder); } } @@ -695,9 +695,9 @@ public void testBooleanHashWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=3, size=")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:BOOLEAN], entries=3, size=")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0 }, new int[] { 0, 1 }, new int[] { 0, 1 }, // Order is not preserved @@ -705,14 +705,14 @@ public void testBooleanHashWithMultiValuedFields() { new int[] { 2 }, new int[] { 0, 1 } ); - assertKeys(ordsAndKeys.keys, false, true, null); + assertKeys(ordsAndKeys.keys(), false, true, null); } else { assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), equalTo("BooleanBlockHash{channel=0, seenFalse=true, seenTrue=true, seenNull=true}") ); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 1 }, new int[] { 1, 2 }, new int[] { 1, 2 }, // Order is not preserved @@ -720,9 +720,9 @@ public void testBooleanHashWithMultiValuedFields() { new int[] { 0 }, new int[] { 1, 2 } ); - assertKeys(ordsAndKeys.keys, null, false, true); + assertKeys(ordsAndKeys.keys(), null, false, true); } - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); }, builder); } } @@ -731,13 +731,13 @@ public void testNullHash() { Object[] values = new Object[] { null, null, null, null }; hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:NULL], entries=1, size=")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:NULL], entries=1, size=")); } else { - assertThat(ordsAndKeys.description, equalTo("NullBlockHash{channel=0, seenNull=true}")); + assertThat(ordsAndKeys.description(), equalTo("NullBlockHash{channel=0, seenNull=true}")); } - assertOrds(ordsAndKeys.ords, 0, 0, 0, 0); - assertThat(ordsAndKeys.nonEmpty, equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(0, 1))); - assertKeys(ordsAndKeys.keys, new Object[][] { new Object[] { null } }); + assertOrds(ordsAndKeys.ords(), 0, 0, 0, 0); + assertThat(ordsAndKeys.nonEmpty(), equalTo(TestBlockFactory.getNonBreakingInstance().newConstantIntVector(0, 1))); + assertKeys(ordsAndKeys.keys(), new Object[][] { new Object[] { null } }); }, blockFactory.newConstantNullBlock(values.length)); } @@ -752,14 +752,14 @@ public void testLongLongHash() { new Object[] { 0L, 1L } }; assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), forcePackedHash ? startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:LONG], entries=4, size=") : equalTo("LongLongBlockHash{channels=[0,1], entries=4}") ); - assertOrds(ordsAndKeys.ords, 0, 1, 0, 2, 3, 2); - assertKeys(ordsAndKeys.keys, expectedKeys); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertOrds(ordsAndKeys.ords(), 0, 1, 0, 2, 3, 2); + assertKeys(ordsAndKeys.keys(), expectedKeys); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, blockFactory.newLongArrayVector(values1, values1.length).asBlock(), blockFactory.newLongArrayVector(values2, values2.length).asBlock() @@ -805,9 +805,9 @@ public void testLongLongHashWithMultiValuedFields() { hash(ordsAndKeys -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:LONG], entries=10, size=")); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:LONG], entries=10, size=")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0, 1, 2, 3 }, new int[] { 0, 2 }, new int[] { 0, 1 }, @@ -819,7 +819,7 @@ public void testLongLongHashWithMultiValuedFields() { new int[] { 6, 0, 7, 2, 8, 9 } ); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, 10L }, new Object[] { 1L, 20L }, @@ -832,11 +832,11 @@ public void testLongLongHashWithMultiValuedFields() { new Object[] { 3L, 30L }, new Object[] { 3L, 10L }, } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 10))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 10))); } else { - assertThat(ordsAndKeys.description, equalTo("LongLongBlockHash{channels=[0,1], entries=8}")); + assertThat(ordsAndKeys.description(), equalTo("LongLongBlockHash{channels=[0,1], entries=8}")); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0, 1, 2, 3 }, new int[] { 0, 2 }, new int[] { 0, 1 }, @@ -848,7 +848,7 @@ public void testLongLongHashWithMultiValuedFields() { new int[] { 4, 0, 5, 2, 6, 7 } ); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, 10L }, new Object[] { 1L, 20L }, @@ -859,7 +859,7 @@ public void testLongLongHashWithMultiValuedFields() { new Object[] { 3L, 30L }, new Object[] { 3L, 10L }, } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 8))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 8))); } }, b1, b2); } @@ -880,19 +880,19 @@ public void testLongLongHashHugeCombinatorialExplosion() { int start = expectedEntries[0]; expectedEntries[0] = Math.min(expectedEntries[0] + pageSize, v1.length * v2.length); assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), forcePackedHash ? startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:LONG], entries=" + expectedEntries[0] + ", size=") : equalTo("LongLongBlockHash{channels=[0,1], entries=" + expectedEntries[0] + "}") ); - assertOrds(ordsAndKeys.ords, IntStream.range(start, expectedEntries[0]).toArray()); + assertOrds(ordsAndKeys.ords(), IntStream.range(start, expectedEntries[0]).toArray()); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), IntStream.range(0, expectedEntries[0]) .mapToObj(i -> new Object[] { v1[i / v2.length], v2[i % v2.length] }) .toArray(l -> new Object[l][]) ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, expectedEntries[0]))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, expectedEntries[0]))); }, pageSize, b1, b2); assertThat("misconfigured test", expectedEntries[0], greaterThan(0)); @@ -904,10 +904,10 @@ public void testIntLongHash() { long[] values2 = new long[] { 0, 0, 0, 1, 1, 1 }; Object[][] expectedKeys = { new Object[] { 0, 0L }, new Object[] { 1, 0L }, new Object[] { 1, 1L }, new Object[] { 0, 1L } }; hash(ordsAndKeys -> { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:INT, 1:LONG], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 0, 2, 3, 2); - assertKeys(ordsAndKeys.keys, expectedKeys); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:INT, 1:LONG], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 0, 2, 3, 2); + assertKeys(ordsAndKeys.keys(), expectedKeys); }, blockFactory.newIntArrayVector(values1, values1.length).asBlock(), blockFactory.newLongArrayVector(values2, values2.length).asBlock() @@ -919,10 +919,10 @@ public void testLongDoubleHash() { double[] values2 = new double[] { 0, 0, 0, 1, 1, 1 }; Object[][] expectedKeys = { new Object[] { 0L, 0d }, new Object[] { 1L, 0d }, new Object[] { 1L, 1d }, new Object[] { 0L, 1d } }; hash((OrdsAndKeys ordsAndKeys) -> { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:DOUBLE], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 0, 2, 3, 2); - assertKeys(ordsAndKeys.keys, expectedKeys); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:DOUBLE], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 0, 2, 3, 2); + assertKeys(ordsAndKeys.keys(), expectedKeys); }, blockFactory.newLongArrayVector(values1, values1.length).asBlock(), blockFactory.newDoubleArrayVector(values2, values2.length).asBlock() @@ -938,10 +938,10 @@ public void testIntBooleanHash() { new Object[] { 1, true }, new Object[] { 0, true } }; hash((OrdsAndKeys ordsAndKeys) -> { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:INT, 1:BOOLEAN], entries=4, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 0, 2, 3, 2); - assertKeys(ordsAndKeys.keys, expectedKeys); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:INT, 1:BOOLEAN], entries=4, size=")); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 0, 2, 3, 2); + assertKeys(ordsAndKeys.keys(), expectedKeys); }, blockFactory.newIntArrayVector(values1, values1.length).asBlock(), blockFactory.newBooleanArrayVector(values2, values2.length).asBlock() @@ -963,10 +963,10 @@ public void testLongLongHashWithNull() { hash((OrdsAndKeys ordsAndKeys) -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:LONG], entries=5, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 3, 4); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:LONG], entries=5, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 3, 4); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, 0L }, new Object[] { null, null }, @@ -974,12 +974,12 @@ public void testLongLongHashWithNull() { new Object[] { 0L, null }, new Object[] { null, 0L }, } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 5))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 5))); } else { - assertThat(ordsAndKeys.description, equalTo("LongLongBlockHash{channels=[0,1], entries=2}")); - assertOrds(ordsAndKeys.ords, 0, null, 1, null, null); - assertKeys(ordsAndKeys.keys, new Object[][] { new Object[] { 1L, 0L }, new Object[] { 0L, 1L } }); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 2))); + assertThat(ordsAndKeys.description(), equalTo("LongLongBlockHash{channels=[0,1], entries=2}")); + assertOrds(ordsAndKeys.ords(), 0, null, 1, null, null); + assertKeys(ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, 0L }, new Object[] { 0L, 1L } }); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 2))); } }, b1, b2); } @@ -1004,17 +1004,17 @@ public void testLongBytesRefHash() { hash((OrdsAndKeys ordsAndKeys) -> { assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), startsWith( forcePackedHash ? "PackedValuesBlockHash{groups=[0:LONG, 1:BYTES_REF], entries=4, size=" : "BytesRefLongBlockHash{keys=[BytesRefKey[channel=1], LongKey[channel=0]], entries=4, size=" ) ); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 0, 2, 3, 2); - assertKeys(ordsAndKeys.keys, expectedKeys); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 0, 2, 3, 2); + assertKeys(ordsAndKeys.keys(), expectedKeys); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, b1, b2); } } @@ -1032,31 +1032,34 @@ public void testLongBytesRefHashWithNull() { hash((OrdsAndKeys ordsAndKeys) -> { if (forcePackedHash) { - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:BYTES_REF], entries=5, size=")); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, 1, 2, 3, 4); + assertThat( + ordsAndKeys.description(), + startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:BYTES_REF], entries=5, size=") + ); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, 1, 2, 3, 4); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, "cat" }, new Object[] { null, null }, new Object[] { 0L, "dog" }, - new Object[] { 1L, null }, + new Object[] { 0L, null }, new Object[] { null, "nn" } } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 5))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 5))); } else { assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), startsWith("BytesRefLongBlockHash{keys=[BytesRefKey[channel=1], LongKey[channel=0]], entries=3, size=") ); - assertThat(ordsAndKeys.description, endsWith("b}")); - assertOrds(ordsAndKeys.ords, 0, null, 1, 2, null); + assertThat(ordsAndKeys.description(), endsWith("b}")); + assertOrds(ordsAndKeys.ords(), 0, null, 1, 2, null); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, "cat" }, new Object[] { 0L, "dog" }, new Object[] { 0L, null } } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 3))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 3))); } }, b1, b2); } @@ -1105,11 +1108,11 @@ public void testLongBytesRefHashWithMultiValuedFields() { hash((OrdsAndKeys ordsAndKeys) -> { if (forcePackedHash) { assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:BYTES_REF], entries=10, size=") ); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0, 1, 2, 3 }, new int[] { 0, 2 }, new int[] { 0, 1 }, @@ -1121,7 +1124,7 @@ public void testLongBytesRefHashWithMultiValuedFields() { new int[] { 6, 0, 7, 2, 8, 9 } ); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, "a" }, new Object[] { 1L, "b" }, @@ -1134,14 +1137,14 @@ public void testLongBytesRefHashWithMultiValuedFields() { new Object[] { 3L, "c" }, new Object[] { 3L, "a" }, } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 10))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 10))); } else { assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), equalTo("BytesRefLongBlockHash{keys=[BytesRefKey[channel=1], LongKey[channel=0]], entries=9, size=483b}") ); assertOrds( - ordsAndKeys.ords, + ordsAndKeys.ords(), new int[] { 0, 1, 2, 3 }, new int[] { 0, 1 }, new int[] { 0, 2 }, @@ -1153,7 +1156,7 @@ public void testLongBytesRefHashWithMultiValuedFields() { new int[] { 5, 6, 7, 0, 1, 8 } ); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), new Object[][] { new Object[] { 1L, "a" }, new Object[] { 2L, "a" }, @@ -1165,7 +1168,7 @@ public void testLongBytesRefHashWithMultiValuedFields() { new Object[] { 3L, "c" }, new Object[] { 3L, "a" }, } ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 9))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 9))); } }, b1, b2); } @@ -1186,7 +1189,7 @@ public void testBytesRefLongHashHugeCombinatorialExplosion() { int start = expectedEntries[0]; expectedEntries[0] = Math.min(expectedEntries[0] + pageSize, v1.length * v2.length); assertThat( - ordsAndKeys.description, + ordsAndKeys.description(), forcePackedHash ? startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:BYTES_REF], entries=" + expectedEntries[0] + ", size=") : startsWith( @@ -1195,9 +1198,9 @@ public void testBytesRefLongHashHugeCombinatorialExplosion() { + ", size=" ) ); - assertOrds(ordsAndKeys.ords, IntStream.range(start, expectedEntries[0]).toArray()); + assertOrds(ordsAndKeys.ords(), IntStream.range(start, expectedEntries[0]).toArray()); assertKeys( - ordsAndKeys.keys, + ordsAndKeys.keys(), IntStream.range(0, expectedEntries[0]) .mapToObj( i -> forcePackedHash @@ -1206,7 +1209,7 @@ public void testBytesRefLongHashHugeCombinatorialExplosion() { ) .toArray(l -> new Object[l][]) ); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, expectedEntries[0]))); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, expectedEntries[0]))); }, pageSize, b1, b2); assertThat("misconfigured test", expectedEntries[0], greaterThan(0)); @@ -1222,10 +1225,10 @@ public void testLongNull() { new Object[] { 2L, null }, new Object[] { 3L, null } }; - assertThat(ordsAndKeys.description, startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:NULL], entries=4, size=")); - assertOrds(ordsAndKeys.ords, 0, 1, 0, 2, 3, 1); - assertKeys(ordsAndKeys.keys, expectedKeys); - assertThat(ordsAndKeys.nonEmpty, equalTo(intRange(0, 4))); + assertThat(ordsAndKeys.description(), startsWith("PackedValuesBlockHash{groups=[0:LONG, 1:NULL], entries=4, size=")); + assertOrds(ordsAndKeys.ords(), 0, 1, 0, 2, 3, 1); + assertKeys(ordsAndKeys.keys(), expectedKeys); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(0, 4))); }, blockFactory.newLongArrayVector(values, values.length).asBlock(), blockFactory.newConstantNullBlock(values.length)); } @@ -1466,8 +1469,6 @@ public void close() { } } - record OrdsAndKeys(String description, int positionOffset, IntBlock ords, Block[] keys, IntVector nonEmpty) {} - /** * Hash some values into a single block of group ids. If the hash produces * more than one block of group ids this will fail. @@ -1495,7 +1496,7 @@ private void hash(Consumer callback, Block... values) { try (ReleasableIterator lookup = hash.lookup(new Page(values), ByteSizeValue.ofKb(between(1, 100)))) { assertThat(lookup.hasNext(), equalTo(true)); try (IntBlock ords = lookup.next()) { - assertThat(ords, equalTo(ordsAndKeys.ords)); + assertThat(ords, equalTo(ordsAndKeys.ords())); } } } @@ -1523,154 +1524,4 @@ private BlockHash buildBlockHash(int emitBatchSize, Block... values) { ? new PackedValuesBlockHash(specs, blockFactory, emitBatchSize) : BlockHash.build(specs, blockFactory, emitBatchSize, true); } - - static void hash(boolean collectKeys, BlockHash blockHash, Consumer callback, Block... values) { - blockHash.add(new Page(values), new GroupingAggregatorFunction.AddInput() { - private void addBlock(int positionOffset, IntBlock groupIds) { - OrdsAndKeys result = new OrdsAndKeys( - blockHash.toString(), - positionOffset, - groupIds, - collectKeys ? blockHash.getKeys() : null, - blockHash.nonEmpty() - ); - - try { - Set allowedOrds = new HashSet<>(); - for (int p = 0; p < result.nonEmpty.getPositionCount(); p++) { - allowedOrds.add(result.nonEmpty.getInt(p)); - } - for (int p = 0; p < result.ords.getPositionCount(); p++) { - if (result.ords.isNull(p)) { - continue; - } - int start = result.ords.getFirstValueIndex(p); - int end = start + result.ords.getValueCount(p); - for (int i = start; i < end; i++) { - int ord = result.ords.getInt(i); - if (false == allowedOrds.contains(ord)) { - fail("ord is not allowed " + ord); - } - } - } - callback.accept(result); - } finally { - Releasables.close(result.keys == null ? null : Releasables.wrap(result.keys), result.nonEmpty); - } - } - - @Override - public void add(int positionOffset, IntArrayBlock groupIds) { - addBlock(positionOffset, groupIds); - } - - @Override - public void add(int positionOffset, IntBigArrayBlock groupIds) { - addBlock(positionOffset, groupIds); - } - - @Override - public void add(int positionOffset, IntVector groupIds) { - addBlock(positionOffset, groupIds.asBlock()); - } - - @Override - public void close() { - fail("hashes should not close AddInput"); - } - }); - if (blockHash instanceof LongLongBlockHash == false - && blockHash instanceof BytesRefLongBlockHash == false - && blockHash instanceof BytesRef2BlockHash == false - && blockHash instanceof BytesRef3BlockHash == false) { - Block[] keys = blockHash.getKeys(); - try (ReleasableIterator lookup = blockHash.lookup(new Page(keys), ByteSizeValue.ofKb(between(1, 100)))) { - while (lookup.hasNext()) { - try (IntBlock ords = lookup.next()) { - for (int p = 0; p < ords.getPositionCount(); p++) { - assertFalse(ords.isNull(p)); - } - } - } - } finally { - Releasables.closeExpectNoException(keys); - } - } - } - - private void assertOrds(IntBlock ordsBlock, Integer... expectedOrds) { - assertOrds(ordsBlock, Arrays.stream(expectedOrds).map(l -> l == null ? null : new int[] { l }).toArray(int[][]::new)); - } - - private void assertOrds(IntBlock ordsBlock, int[]... expectedOrds) { - assertEquals(expectedOrds.length, ordsBlock.getPositionCount()); - for (int p = 0; p < expectedOrds.length; p++) { - int start = ordsBlock.getFirstValueIndex(p); - int count = ordsBlock.getValueCount(p); - if (expectedOrds[p] == null) { - if (false == ordsBlock.isNull(p)) { - StringBuilder error = new StringBuilder(); - error.append(p); - error.append(": expected null but was ["); - for (int i = 0; i < count; i++) { - if (i != 0) { - error.append(", "); - } - error.append(ordsBlock.getInt(start + i)); - } - fail(error.append("]").toString()); - } - continue; - } - assertFalse(p + ": expected not null", ordsBlock.isNull(p)); - int[] actual = new int[count]; - for (int i = 0; i < count; i++) { - actual[i] = ordsBlock.getInt(start + i); - } - assertThat("position " + p, actual, equalTo(expectedOrds[p])); - } - } - - private void assertKeys(Block[] actualKeys, Object... expectedKeys) { - Object[][] flipped = new Object[expectedKeys.length][]; - for (int r = 0; r < flipped.length; r++) { - flipped[r] = new Object[] { expectedKeys[r] }; - } - assertKeys(actualKeys, flipped); - } - - private void assertKeys(Block[] actualKeys, Object[][] expectedKeys) { - for (int r = 0; r < expectedKeys.length; r++) { - assertThat(actualKeys, arrayWithSize(expectedKeys[r].length)); - } - for (int c = 0; c < actualKeys.length; c++) { - assertThat("block " + c, actualKeys[c].getPositionCount(), equalTo(expectedKeys.length)); - } - for (int r = 0; r < expectedKeys.length; r++) { - for (int c = 0; c < actualKeys.length; c++) { - if (expectedKeys[r][c] == null) { - assertThat("expected null", actualKeys[c].isNull(r), equalTo(true)); - return; - } - assertThat(actualKeys[c].isNull(r), equalTo(false)); - if (expectedKeys[r][c] instanceof Integer v) { - assertThat(((IntBlock) actualKeys[c]).getInt(r), equalTo(v)); - } else if (expectedKeys[r][c] instanceof Long v) { - assertThat(((LongBlock) actualKeys[c]).getLong(r), equalTo(v)); - } else if (expectedKeys[r][c] instanceof Double v) { - assertThat(((DoubleBlock) actualKeys[c]).getDouble(r), equalTo(v)); - } else if (expectedKeys[r][c] instanceof String v) { - assertThat(((BytesRefBlock) actualKeys[c]).getBytesRef(r, new BytesRef()), equalTo(new BytesRef(v))); - } else if (expectedKeys[r][c] instanceof Boolean v) { - assertThat(((BooleanBlock) actualKeys[c]).getBoolean(r), equalTo(v)); - } else { - throw new IllegalArgumentException("unsupported type " + expectedKeys[r][c].getClass()); - } - } - } - } - - IntVector intRange(int startInclusive, int endExclusive) { - return IntVector.range(startInclusive, endExclusive, TestBlockFactory.getNonBreakingInstance()); - } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/TopNBlockHashTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/TopNBlockHashTests.java new file mode 100644 index 0000000000000..f96b9d26f075c --- /dev/null +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/blockhash/TopNBlockHashTests.java @@ -0,0 +1,393 @@ +/* + * 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.compute.aggregation.blockhash; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.IntBlock; +import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.compute.data.Page; +import org.elasticsearch.core.ReleasableIterator; +import org.elasticsearch.core.Releasables; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; + +public class TopNBlockHashTests extends BlockHashTestCase { + + private static final int LIMIT_TWO = 2; + private static final int LIMIT_HIGH = 10000; + + @ParametersFactory + public static List params() { + List params = new ArrayList<>(); + + // TODO: Uncomment this "true" when implemented + for (boolean forcePackedHash : new boolean[] { /*true,*/false }) { + for (boolean asc : new boolean[] { true, false }) { + for (boolean nullsFirst : new boolean[] { true, false }) { + for (int limit : new int[] { LIMIT_TWO, LIMIT_HIGH }) { + params.add(new Object[] { forcePackedHash, asc, nullsFirst, limit }); + } + } + } + } + + return params; + } + + private final boolean forcePackedHash; + private final boolean asc; + private final boolean nullsFirst; + private final int limit; + + public TopNBlockHashTests( + @Name("forcePackedHash") boolean forcePackedHash, + @Name("asc") boolean asc, + @Name("nullsFirst") boolean nullsFirst, + @Name("limit") int limit + ) { + this.forcePackedHash = forcePackedHash; + this.asc = asc; + this.nullsFirst = nullsFirst; + this.limit = limit; + } + + public void testLongHash() { + long[] values = new long[] { 2, 1, 4, 2, 4, 1, 3, 4 }; + + hash(ordsAndKeys -> { + if (forcePackedHash) { + // TODO: Not tested yet + } else { + assertThat( + ordsAndKeys.description(), + equalTo("LongTopNBlockHash{channel=0, " + topNParametersString(4, 0) + ", hasNull=false}") + ); + if (limit == LIMIT_HIGH) { + assertKeys(ordsAndKeys.keys(), 2L, 1L, 4L, 3L); + assertOrds(ordsAndKeys.ords(), 1, 2, 3, 1, 3, 2, 4, 3); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 5))); + } else { + if (asc) { + assertKeys(ordsAndKeys.keys(), 2L, 1L); + assertOrds(ordsAndKeys.ords(), 1, 2, null, 1, null, 2, null, null); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(1, 2))); + } else { + assertKeys(ordsAndKeys.keys(), 4L, 3L); + assertOrds(ordsAndKeys.ords(), null, null, 1, null, 1, null, 2, 1); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(1, 2))); + } + } + } + }, blockFactory.newLongArrayVector(values, values.length).asBlock()); + } + + public void testLongHashBatched() { + long[][] arrays = { new long[] { 2, 1, 4, 2 }, new long[] { 4, 1, 3, 4 } }; + + hashBatchesCallbackOnLast(ordsAndKeys -> { + if (forcePackedHash) { + // TODO: Not tested yet + } else { + assertThat( + ordsAndKeys.description(), + equalTo("LongTopNBlockHash{channel=0, " + topNParametersString(4, asc ? 0 : 1) + ", hasNull=false}") + ); + if (limit == LIMIT_HIGH) { + assertKeys(ordsAndKeys.keys(), 2L, 1L, 4L, 3L); + assertOrds(ordsAndKeys.ords(), 3, 2, 4, 3); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intRange(1, 5))); + } else { + if (asc) { + assertKeys(ordsAndKeys.keys(), 2L, 1L); + assertOrds(ordsAndKeys.ords(), null, 2, null, null); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(1, 2))); + } else { + assertKeys(ordsAndKeys.keys(), 4L, 3L); + assertOrds(ordsAndKeys.ords(), 2, null, 3, 2); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(2, 3))); + } + } + } + }, + Arrays.stream(arrays) + .map(array -> new Block[] { blockFactory.newLongArrayVector(array, array.length).asBlock() }) + .toArray(Block[][]::new) + ); + } + + public void testLongHashWithNulls() { + try (LongBlock.Builder builder = blockFactory.newLongBlockBuilder(4)) { + builder.appendLong(0); + builder.appendNull(); + builder.appendLong(2); + builder.appendNull(); + + hash(ordsAndKeys -> { + if (forcePackedHash) { + // TODO: Not tested yet + } else { + boolean hasTwoNonNullValues = nullsFirst == false || limit == LIMIT_HIGH; + boolean hasNull = nullsFirst || limit == LIMIT_HIGH; + assertThat( + ordsAndKeys.description(), + equalTo( + "LongTopNBlockHash{channel=0, " + + topNParametersString(hasTwoNonNullValues ? 2 : 1, 0) + + ", hasNull=" + + hasNull + + "}" + ) + ); + if (limit == LIMIT_HIGH) { + assertKeys(ordsAndKeys.keys(), null, 0L, 2L); + assertOrds(ordsAndKeys.ords(), 1, 0, 2, 0); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(0, 1, 2))); + } else { + if (nullsFirst) { + if (asc) { + assertKeys(ordsAndKeys.keys(), null, 0L); + assertOrds(ordsAndKeys.ords(), 1, 0, null, 0); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(0, 1))); + } else { + assertKeys(ordsAndKeys.keys(), null, 2L); + assertOrds(ordsAndKeys.ords(), null, 0, 1, 0); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(0, 1))); + } + } else { + assertKeys(ordsAndKeys.keys(), 0L, 2L); + assertOrds(ordsAndKeys.ords(), 1, null, 2, null); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(1, 2))); + } + } + } + }, builder); + } + } + + public void testLongHashWithMultiValuedFields() { + try (LongBlock.Builder builder = blockFactory.newLongBlockBuilder(8)) { + builder.appendLong(1); + builder.beginPositionEntry(); + builder.appendLong(1); + builder.appendLong(2); + builder.appendLong(3); + builder.endPositionEntry(); + builder.beginPositionEntry(); + builder.appendLong(1); + builder.appendLong(1); + builder.endPositionEntry(); + builder.beginPositionEntry(); + builder.appendLong(3); + builder.endPositionEntry(); + builder.appendNull(); + builder.beginPositionEntry(); + builder.appendLong(3); + builder.appendLong(2); + builder.appendLong(1); + builder.endPositionEntry(); + + hash(ordsAndKeys -> { + if (forcePackedHash) { + // TODO: Not tested yet + } else { + if (limit == LIMIT_HIGH) { + assertThat( + ordsAndKeys.description(), + equalTo("LongTopNBlockHash{channel=0, " + topNParametersString(3, 0) + ", hasNull=true}") + ); + assertOrds( + ordsAndKeys.ords(), + new int[] { 1 }, + new int[] { 1, 2, 3 }, + new int[] { 1 }, + new int[] { 3 }, + new int[] { 0 }, + new int[] { 3, 2, 1 } + ); + assertKeys(ordsAndKeys.keys(), null, 1L, 2L, 3L); + } else { + assertThat( + ordsAndKeys.description(), + equalTo( + "LongTopNBlockHash{channel=0, " + + topNParametersString(nullsFirst ? 1 : 2, 0) + + ", hasNull=" + + nullsFirst + + "}" + ) + ); + if (nullsFirst) { + if (asc) { + assertKeys(ordsAndKeys.keys(), null, 1L); + assertOrds( + ordsAndKeys.ords(), + new int[] { 1 }, + new int[] { 1 }, + new int[] { 1 }, + null, + new int[] { 0 }, + new int[] { 1 } + ); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(0, 1))); + } else { + assertKeys(ordsAndKeys.keys(), null, 3L); + assertOrds( + ordsAndKeys.ords(), + null, + new int[] { 1 }, + null, + new int[] { 1 }, + new int[] { 0 }, + new int[] { 1 } + ); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(0, 1))); + } + } else { + if (asc) { + assertKeys(ordsAndKeys.keys(), 1L, 2L); + assertOrds( + ordsAndKeys.ords(), + new int[] { 1 }, + new int[] { 1, 2 }, + new int[] { 1 }, + null, + null, + new int[] { 2, 1 } + ); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(1, 2))); + } else { + assertKeys(ordsAndKeys.keys(), 2L, 3L); + assertOrds(ordsAndKeys.ords(), null, new int[] { 1, 2 }, null, new int[] { 2 }, null, new int[] { 2, 1 }); + assertThat(ordsAndKeys.nonEmpty(), equalTo(intVector(1, 2))); + } + } + } + } + }, builder); + } + } + + // TODO: Test adding multiple blocks, as it triggers different logics like: + // - Keeping older unused ords + // - Returning nonEmpty ords greater than 1 + + /** + * Hash some values into a single block of group ids. If the hash produces + * more than one block of group ids this will fail. + */ + private void hash(Consumer callback, Block.Builder... values) { + hash(callback, Block.Builder.buildAll(values)); + } + + /** + * Hash some values into a single block of group ids. If the hash produces + * more than one block of group ids this will fail. + */ + private void hash(Consumer callback, Block... values) { + boolean[] called = new boolean[] { false }; + try (BlockHash hash = buildBlockHash(16 * 1024, values)) { + hash(true, hash, ordsAndKeys -> { + if (called[0]) { + throw new IllegalStateException("hash produced more than one block"); + } + called[0] = true; + callback.accept(ordsAndKeys); + try (ReleasableIterator lookup = hash.lookup(new Page(values), ByteSizeValue.ofKb(between(1, 100)))) { + assertThat(lookup.hasNext(), equalTo(true)); + try (IntBlock ords = lookup.next()) { + assertThat(ords, equalTo(ordsAndKeys.ords())); + } + } + }, values); + } finally { + Releasables.close(values); + } + } + + // TODO: Randomize this instead? + /** + * Hashes multiple separated batches of values. + * + * @param callback Callback with the OrdsAndKeys for the last batch + */ + private void hashBatchesCallbackOnLast(Consumer callback, Block[]... batches) { + // Ensure all batches share the same specs + assertThat(batches.length, greaterThan(0)); + for (Block[] batch : batches) { + assertThat(batch.length, equalTo(batches[0].length)); + for (int i = 0; i < batch.length; i++) { + assertThat(batches[0][i].elementType(), equalTo(batch[i].elementType())); + } + } + + boolean[] called = new boolean[] { false }; + try (BlockHash hash = buildBlockHash(16 * 1024, batches[0])) { + for (Block[] batch : batches) { + called[0] = false; + hash(true, hash, ordsAndKeys -> { + if (called[0]) { + throw new IllegalStateException("hash produced more than one block"); + } + called[0] = true; + if (batch == batches[batches.length - 1]) { + callback.accept(ordsAndKeys); + } + try (ReleasableIterator lookup = hash.lookup(new Page(batch), ByteSizeValue.ofKb(between(1, 100)))) { + assertThat(lookup.hasNext(), equalTo(true)); + try (IntBlock ords = lookup.next()) { + assertThat(ords, equalTo(ordsAndKeys.ords())); + } + } + }, batch); + } + } finally { + Releasables.close(Arrays.stream(batches).flatMap(Arrays::stream).toList()); + } + } + + private BlockHash buildBlockHash(int emitBatchSize, Block... values) { + List specs = new ArrayList<>(values.length); + for (int c = 0; c < values.length; c++) { + specs.add(new BlockHash.GroupSpec(c, values[c].elementType(), false, topNDef(c))); + } + assert forcePackedHash == false : "Packed TopN hash not implemented yet"; + /*return forcePackedHash + ? new PackedValuesBlockHash(specs, blockFactory, emitBatchSize) + : BlockHash.build(specs, blockFactory, emitBatchSize, true);*/ + + return new LongTopNBlockHash(specs.get(0).channel(), asc, nullsFirst, limit, blockFactory); + } + + /** + * Returns the common toString() part of the TopNBlockHash using the test parameters. + */ + private String topNParametersString(int differentValues, int unusedInsertedValues) { + return "asc=" + + asc + + ", nullsFirst=" + + nullsFirst + + ", limit=" + + limit + + ", entries=" + + Math.min(differentValues, limit + unusedInsertedValues); + } + + private BlockHash.TopNDef topNDef(int order) { + return new BlockHash.TopNDef(order, asc, nullsFirst, limit); + } +} diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/LongTopNSetTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/LongTopNSetTests.java new file mode 100644 index 0000000000000..364190fdb5d5a --- /dev/null +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/LongTopNSetTests.java @@ -0,0 +1,52 @@ +/* + * 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.compute.data.sort; + +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.search.sort.SortOrder; + +import java.util.List; + +public class LongTopNSetTests extends TopNSetTestCase { + + @Override + protected LongTopNSet build(BigArrays bigArrays, SortOrder sortOrder, int limit) { + return new LongTopNSet(bigArrays, sortOrder, limit); + } + + @Override + protected Long randomValue() { + return randomLong(); + } + + @Override + protected List threeSortedValues() { + return List.of(Long.MIN_VALUE, randomLong(), Long.MAX_VALUE); + } + + @Override + protected void collect(LongTopNSet sort, Long value) { + sort.collect(value); + } + + @Override + protected void reduceLimitByOne(LongTopNSet sort) { + sort.reduceLimitByOne(); + } + + @Override + protected Long getWorstValue(LongTopNSet sort) { + return sort.getWorstValue(); + } + + @Override + protected int getCount(LongTopNSet sort) { + return sort.getCount(); + } + +} diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java new file mode 100644 index 0000000000000..0df1d6047a30e --- /dev/null +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java @@ -0,0 +1,215 @@ +/* + * 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.compute.data.sort; + +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.breaker.CircuitBreakingException; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.MockBigArrays; +import org.elasticsearch.common.util.MockPageCacheRecycler; +import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.core.Releasable; +import org.elasticsearch.indices.CrankyCircuitBreakerService; +import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; +import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.test.ESTestCase; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; + +public abstract class TopNSetTestCase> extends ESTestCase { + /** + * Build a {@link T} to test. Sorts built by this method shouldn't need scores. + */ + protected abstract T build(BigArrays bigArrays, SortOrder sortOrder, int limit); + + private T build(SortOrder sortOrder, int limit) { + return build(bigArrays(), sortOrder, limit); + } + + /** + * A random value for testing, with the appropriate precision for the type we're testing. + */ + protected abstract V randomValue(); + + /** + * Returns a list of 3 values, in ascending order. + */ + protected abstract List threeSortedValues(); + + /** + * Collect a value into the top. + * + * @param value value to collect, always sent as double just to have + * a number to test. Subclasses should cast to their favorite types + */ + protected abstract void collect(T sort, V value); + + protected abstract void reduceLimitByOne(T sort); + + protected abstract V getWorstValue(T sort); + + protected abstract int getCount(T sort); + + public final void testNeverCalled() { + SortOrder sortOrder = randomFrom(SortOrder.values()); + int limit = randomIntBetween(0, 10); + try (T sort = build(sortOrder, limit)) { + assertResults(sort, sortOrder, limit, List.of()); + } + } + + public final void testLimit0() { + SortOrder sortOrder = randomFrom(SortOrder.values()); + int limit = 0; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(0)); + collect(sort, values.get(1)); + + assertResults(sort, sortOrder, limit, List.of()); + } + } + + public final void testSingleValue() { + SortOrder sortOrder = randomFrom(SortOrder.values()); + int limit = 1; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(0)); + + assertResults(sort, sortOrder, limit, List.of(values.get(0))); + } + } + + public final void testNonCompetitive() { + SortOrder sortOrder = SortOrder.DESC; + int limit = 1; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(1)); + collect(sort, values.get(0)); + + assertResults(sort, sortOrder, limit, List.of(values.get(1))); + } + } + + public final void testCompetitive() { + SortOrder sortOrder = SortOrder.DESC; + int limit = 1; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(0)); + collect(sort, values.get(1)); + + assertResults(sort, sortOrder, limit, List.of(values.get(1))); + } + } + + public final void testTwoHitsDesc() { + SortOrder sortOrder = SortOrder.DESC; + int limit = 2; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(0)); + collect(sort, values.get(1)); + collect(sort, values.get(2)); + + assertResults(sort, sortOrder, limit, List.of(values.get(2), values.get(1))); + } + } + + public final void testTwoHitsAsc() { + SortOrder sortOrder = SortOrder.ASC; + int limit = 2; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(0)); + collect(sort, values.get(1)); + collect(sort, values.get(2)); + + assertResults(sort, sortOrder, limit, List.of(values.get(0), values.get(1))); + } + } + + public final void testReduceLimit() { + SortOrder sortOrder = randomFrom(SortOrder.values()); + int limit = 3; + try (T sort = build(sortOrder, limit)) { + var values = threeSortedValues(); + + collect(sort, values.get(0)); + collect(sort, values.get(1)); + collect(sort, values.get(2)); + + assertResults(sort, sortOrder, limit, values); + + reduceLimitByOne(sort); + collect(sort, values.get(2)); + + assertResults(sort, sortOrder, limit - 1, values); + } + } + + public final void testCrankyBreaker() { + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, new CrankyCircuitBreakerService()); + SortOrder sortOrder = randomFrom(SortOrder.values()); + int limit = randomIntBetween(0, 3); + + try (T sort = build(bigArrays, sortOrder, limit)) { + List values = new ArrayList<>(); + + for (int i = 0; i < randomIntBetween(0, 4); i++) { + V value = randomValue(); + values.add(value); + collect(sort, value); + } + + if (randomBoolean() && limit > 0) { + reduceLimitByOne(sort); + limit--; + + V value = randomValue(); + values.add(value); + collect(sort, value); + } + + assertResults(sort, sortOrder, limit - 1, values); + } catch (CircuitBreakingException e) { + assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE)); + } + assertThat(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST).getUsed(), equalTo(0L)); + } + + protected void assertResults(T sort, SortOrder sortOrder, int limit, List values) { + var sortedUniqueValues = values.stream() + .distinct() + .sorted(sortOrder == SortOrder.ASC ? Comparator.naturalOrder() : Comparator.reverseOrder()) + .limit(limit) + .toList(); + + assertEquals(sortedUniqueValues.size(), getCount(sort)); + if (sortedUniqueValues.isEmpty() == false) { + assertEquals(sortedUniqueValues.getLast(), getWorstValue(sort)); + } + } + + private BigArrays bigArrays() { + return new MockBigArrays(new MockPageCacheRecycler(Settings.EMPTY), new NoneCircuitBreakerService()); + } +} diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java index 0bacb36e00066..ec84d17045af4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java @@ -17,12 +17,16 @@ import org.elasticsearch.compute.aggregation.blockhash.BlockHash; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BlockFactory; +import org.elasticsearch.compute.data.BlockUtils; import org.elasticsearch.compute.data.ElementType; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; +import org.elasticsearch.compute.test.BlockTestUtils; import org.elasticsearch.core.Tuple; import org.hamcrest.Matcher; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.stream.LongStream; @@ -96,4 +100,158 @@ protected void assertSimpleOutput(List input, List results) { max.assertSimpleGroup(input, maxs, i, group); } } + + public void testTopNNullsLast() { + boolean ascOrder = randomBoolean(); + var groups = new Long[] { 0L, 10L, 20L, 30L, 40L, 50L }; + if (ascOrder) { + Arrays.sort(groups, Comparator.reverseOrder()); + } + var mode = AggregatorMode.SINGLE; + var groupChannel = 0; + var aggregatorChannels = List.of(1); + + try ( + var operator = new HashAggregationOperator.HashAggregationOperatorFactory( + List.of(new BlockHash.GroupSpec(groupChannel, ElementType.LONG, false, new BlockHash.TopNDef(0, ascOrder, false, 3))), + mode, + List.of( + new SumLongAggregatorFunctionSupplier().groupingAggregatorFactory(mode, aggregatorChannels), + new MaxLongAggregatorFunctionSupplier().groupingAggregatorFactory(mode, aggregatorChannels) + ), + randomPageSize(), + null + ).get(driverContext()) + ) { + var page = new Page( + BlockUtils.fromList( + blockFactory(), + List.of( + List.of(groups[1], 2L), + Arrays.asList(null, 1L), + List.of(groups[2], 4L), + List.of(groups[3], 8L), + List.of(groups[3], 16L) + ) + ) + ); + operator.addInput(page); + + page = new Page( + BlockUtils.fromList( + blockFactory(), + List.of( + List.of(groups[5], 64L), + List.of(groups[4], 32L), + List.of(List.of(groups[1], groups[5]), 128L), + List.of(groups[0], 256L), + Arrays.asList(null, 512L) + ) + ) + ); + operator.addInput(page); + + operator.finish(); + + var outputPage = operator.getOutput(); + + var groupsBlock = (LongBlock) outputPage.getBlock(0); + var sumBlock = (LongBlock) outputPage.getBlock(1); + var maxBlock = (LongBlock) outputPage.getBlock(2); + + assertThat(groupsBlock.getPositionCount(), equalTo(3)); + assertThat(sumBlock.getPositionCount(), equalTo(3)); + assertThat(maxBlock.getPositionCount(), equalTo(3)); + + assertThat(groupsBlock.getTotalValueCount(), equalTo(3)); + assertThat(sumBlock.getTotalValueCount(), equalTo(3)); + assertThat(maxBlock.getTotalValueCount(), equalTo(3)); + + assertThat( + BlockTestUtils.valuesAtPositions(groupsBlock, 0, 3), + equalTo(List.of(List.of(groups[3]), List.of(groups[5]), List.of(groups[4]))) + ); + assertThat(BlockTestUtils.valuesAtPositions(sumBlock, 0, 3), equalTo(List.of(List.of(24L), List.of(192L), List.of(32L)))); + assertThat(BlockTestUtils.valuesAtPositions(maxBlock, 0, 3), equalTo(List.of(List.of(16L), List.of(128L), List.of(32L)))); + + outputPage.releaseBlocks(); + } + } + + public void testTopNNullsFirst() { + boolean ascOrder = randomBoolean(); + var groups = new Long[] { 0L, 10L, 20L, 30L, 40L, 50L }; + if (ascOrder) { + Arrays.sort(groups, Comparator.reverseOrder()); + } + var mode = AggregatorMode.SINGLE; + var groupChannel = 0; + var aggregatorChannels = List.of(1); + + try ( + var operator = new HashAggregationOperator.HashAggregationOperatorFactory( + List.of(new BlockHash.GroupSpec(groupChannel, ElementType.LONG, false, new BlockHash.TopNDef(0, ascOrder, true, 3))), + mode, + List.of( + new SumLongAggregatorFunctionSupplier().groupingAggregatorFactory(mode, aggregatorChannels), + new MaxLongAggregatorFunctionSupplier().groupingAggregatorFactory(mode, aggregatorChannels) + ), + randomPageSize(), + null + ).get(driverContext()) + ) { + var page = new Page( + BlockUtils.fromList( + blockFactory(), + List.of( + List.of(groups[1], 2L), + Arrays.asList(null, 1L), + List.of(groups[2], 4L), + List.of(groups[3], 8L), + List.of(groups[3], 16L) + ) + ) + ); + operator.addInput(page); + + page = new Page( + BlockUtils.fromList( + blockFactory(), + List.of( + List.of(groups[5], 64L), + List.of(groups[4], 32L), + List.of(List.of(groups[1], groups[5]), 128L), + List.of(groups[0], 256L), + Arrays.asList(null, 512L) + ) + ) + ); + operator.addInput(page); + + operator.finish(); + + var outputPage = operator.getOutput(); + + var groupsBlock = (LongBlock) outputPage.getBlock(0); + var sumBlock = (LongBlock) outputPage.getBlock(1); + var maxBlock = (LongBlock) outputPage.getBlock(2); + + assertThat(groupsBlock.getPositionCount(), equalTo(3)); + assertThat(sumBlock.getPositionCount(), equalTo(3)); + assertThat(maxBlock.getPositionCount(), equalTo(3)); + + assertThat(groupsBlock.getTotalValueCount(), equalTo(2)); + assertThat(sumBlock.getTotalValueCount(), equalTo(3)); + assertThat(maxBlock.getTotalValueCount(), equalTo(3)); + + assertThat( + BlockTestUtils.valuesAtPositions(groupsBlock, 0, 3), + equalTo(Arrays.asList(null, List.of(groups[5]), List.of(groups[4]))) + ); + assertThat(BlockTestUtils.valuesAtPositions(sumBlock, 0, 3), equalTo(List.of(List.of(513L), List.of(192L), List.of(32L)))); + assertThat(BlockTestUtils.valuesAtPositions(maxBlock, 0, 3), equalTo(List.of(List.of(512L), List.of(128L), List.of(32L)))); + + outputPage.releaseBlocks(); + } + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/AbstractPhysicalOperationProviders.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/AbstractPhysicalOperationProviders.java index 036db6335e36e..a78a15bf1ca48 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/AbstractPhysicalOperationProviders.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/AbstractPhysicalOperationProviders.java @@ -354,7 +354,7 @@ BlockHash.GroupSpec toHashGroupSpec() { throw new EsqlIllegalArgumentException("planned to use ordinals but tried to use the hash instead"); } - return new BlockHash.GroupSpec(channel, elementType(), Alias.unwrap(expression) instanceof Categorize); + return new BlockHash.GroupSpec(channel, elementType(), Alias.unwrap(expression) instanceof Categorize, null); } ElementType elementType() { From 601634d127adf0ff1ea3d26f02b15f2ca071f891 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 11 Jun 2025 14:21:48 +0200 Subject: [PATCH 063/102] Add "Searchable Snapshots" to changelog validation schema (#129180) We created a new ":Distributed Indexing/Searchable Snapshots" label recently on Github, so I think it makes sense to also have a "Searchable Snapshots" label in the changelog. It also makes sense since there is automatic changelog generation based on the pull request label. --- build-tools-internal/src/main/resources/changelog-schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/build-tools-internal/src/main/resources/changelog-schema.json b/build-tools-internal/src/main/resources/changelog-schema.json index 7229571fc8bf4..24189a3c50106 100644 --- a/build-tools-internal/src/main/resources/changelog-schema.json +++ b/build-tools-internal/src/main/resources/changelog-schema.json @@ -86,6 +86,7 @@ "Rollup", "SQL", "Search", + "Searchable Snapshots", "Security", "Snapshot/Restore", "Stats", From 4000c4e1492c9f11cc638876a6b84c3434c889b3 Mon Sep 17 00:00:00 2001 From: Alexander Spies Date: Wed, 11 Jun 2025 14:39:21 +0200 Subject: [PATCH 064/102] ESQL: Fix FieldAttribute name usage in InferNonNullAggConstraint (#128910) * Fix InferNonNullAggConstraint with union types * Begin fixing LucenePushdownPredicates with union types * Introduce a dedicated wrapper record FieldName to be used where field names are really required. The fixes consist of using FieldAttribute.fieldName() instead of .name() or .field().name(). .name() can be some temporary string unrelated to the actual name of the Lucene index field, whereas .field().name() doesn't know about parent fields; .fieldName() gives the full field name (from the root of the document). The biggest offender of such misuse is SearchStats; make this always require a FieldName, not a String - and make FieldAttribute#fieldName handily return an instance of FieldName so users of SearchStats don't accidentally use the return value of FieldAttribute#name. --- docs/changelog/128910.yaml | 5 ++ .../esql/core/expression/FieldAttribute.java | 41 ++++++++---- .../xpack/esql/core/type/EsField.java | 2 +- .../xpack/esql/EsqlTestUtils.java | 41 ++++++------ .../src/main/resources/union_types.csv-spec | 13 ++++ .../xpack/esql/analysis/Analyzer.java | 2 +- .../function/UnsupportedAttribute.java | 11 ++-- .../local/InferNonNullAggConstraint.java | 4 +- .../local/LucenePushdownPredicates.java | 16 +++-- .../physical/local/PushStatsToSource.java | 2 +- .../planner/EsPhysicalOperationProviders.java | 2 +- .../xpack/esql/stats/SearchContextStats.java | 64 +++++++++++-------- .../xpack/esql/stats/SearchStats.java | 44 +++++++------ .../LocalLogicalPlanOptimizerTests.java | 63 ++++++++++++++++-- .../LocalPhysicalPlanOptimizerTests.java | 18 +++--- .../optimizer/LogicalPlanOptimizerTests.java | 2 +- .../optimizer/PhysicalPlanOptimizerTests.java | 25 ++++---- .../physical/local/PushTopNToSourceTests.java | 2 +- .../TestPhysicalOperationProviders.java | 2 +- .../xpack/esql/stats/DisabledSearchStats.java | 21 +++--- 20 files changed, 245 insertions(+), 135 deletions(-) create mode 100644 docs/changelog/128910.yaml diff --git a/docs/changelog/128910.yaml b/docs/changelog/128910.yaml new file mode 100644 index 0000000000000..e81a1b5996484 --- /dev/null +++ b/docs/changelog/128910.yaml @@ -0,0 +1,5 @@ +pr: 128910 +summary: Fix `FieldAttribute` name usage in `InferNonNullAggConstraint` +area: ES|QL +type: bug +issues: [] diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java index 0632f21436c2a..832745f7cb15e 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java @@ -27,14 +27,24 @@ /** * Attribute for an ES field. - * To differentiate between the different type of fields this class offers: - * - name - the fully qualified name (foo.bar.tar) - * - path - the path pointing to the field name (foo.bar) - * - parent - the immediate parent of the field; useful for figuring out the type of field (nested vs object) - * - nestedParent - if nested, what's the parent (which might not be the immediate one) + * This class offers: + * - name - the name of the attribute, but not necessarily of the field. + * - The raw EsField representing the field; for parent.child.grandchild this is just grandchild. + * - parentName - the full path to the immediate parent of the field, e.g. parent.child (without .grandchild) + * + * To adequately represent e.g. union types, the name of the attribute can be altered because we may have multiple synthetic field + * attributes that really belong to the same underlying field. For instance, if a multi-typed field is used both as {@code field::string} + * and {@code field::ip}, we'll generate 2 field attributes called {@code $$field$converted_to$string} and {@code $$field$converted_to$ip} + * but still referring to the same underlying field. */ public class FieldAttribute extends TypedAttribute { + /** + * A field name, as found in the mapping. Includes the whole path from the root of the document. + * Implemented as a wrapper around {@link String} to distinguish from the attribute name (which sometimes differs!) at compile time. + */ + public record FieldName(String string) {}; + static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Attribute.class, "FieldAttribute", @@ -43,6 +53,7 @@ public class FieldAttribute extends TypedAttribute { private final String parentName; private final EsField field; + protected FieldName lazyFieldName; public FieldAttribute(Source source, String name, EsField field) { this(source, null, name, field); @@ -136,15 +147,19 @@ public String parentName() { /** * The full name of the field in the index, including all parent fields. E.g. {@code parent.subfield.this_field}. */ - public String fieldName() { - // Before 8.15, the field name was the same as the attribute's name. - // On later versions, the attribute can be renamed when creating synthetic attributes. - // Because until 8.15, we couldn't set `synthetic` to true due to a bug, in that version such FieldAttributes are marked by their - // name starting with `$$`. - if ((synthetic() || name().startsWith(SYNTHETIC_ATTRIBUTE_NAME_PREFIX)) == false) { - return name(); + public FieldName fieldName() { + if (lazyFieldName == null) { + // Before 8.15, the field name was the same as the attribute's name. + // On later versions, the attribute can be renamed when creating synthetic attributes. + // Because until 8.15, we couldn't set `synthetic` to true due to a bug, in that version such FieldAttributes are marked by + // their + // name starting with `$$`. + if ((synthetic() || name().startsWith(SYNTHETIC_ATTRIBUTE_NAME_PREFIX)) == false) { + lazyFieldName = new FieldName(name()); + } + lazyFieldName = new FieldName(Strings.hasText(parentName) ? parentName + "." + field.getName() : field.getName()); } - return Strings.hasText(parentName) ? parentName + "." + field.getName() : field.getName(); + return lazyFieldName; } public EsField.Exact getExactInfo() { diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java index 321c79ee13a83..6a46cb979e105 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/EsField.java @@ -117,7 +117,7 @@ public String getWriteableName() { } /** - * Returns the field path + * Returns the simple name, but not the full field path. The latter requires knowing the path of the parent field. */ public String getName() { return name; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java index ebada01e72b41..7b5843704eac8 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java @@ -52,6 +52,7 @@ import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute.FieldName; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; @@ -248,22 +249,22 @@ public static EsRelation relation() { public static class TestSearchStats implements SearchStats { @Override - public boolean exists(String field) { + public boolean exists(FieldName field) { return true; } @Override - public boolean isIndexed(String field) { + public boolean isIndexed(FieldName field) { return exists(field); } @Override - public boolean hasDocValues(String field) { + public boolean hasDocValues(FieldName field) { return exists(field); } @Override - public boolean hasExactSubfield(String field) { + public boolean hasExactSubfield(FieldName field) { return exists(field); } @@ -273,32 +274,32 @@ public long count() { } @Override - public long count(String field) { + public long count(FieldName field) { return exists(field) ? -1 : 0; } @Override - public long count(String field, BytesRef value) { + public long count(FieldName field, BytesRef value) { return exists(field) ? -1 : 0; } @Override - public byte[] min(String field, DataType dataType) { + public byte[] min(FieldName field, DataType dataType) { return null; } @Override - public byte[] max(String field, DataType dataType) { + public byte[] max(FieldName field, DataType dataType) { return null; } @Override - public boolean isSingleValue(String field) { + public boolean isSingleValue(FieldName field) { return false; } @Override - public boolean canUseEqualityOnSyntheticSourceDelegate(String name, String value) { + public boolean canUseEqualityOnSyntheticSourceDelegate(FieldName name, String value) { return false; } } @@ -349,23 +350,23 @@ private boolean isConfigationSet(Config config, String field) { } @Override - public boolean exists(String field) { - return isConfigationSet(Config.EXISTS, field); + public boolean exists(FieldName field) { + return isConfigationSet(Config.EXISTS, field.string()); } @Override - public boolean isIndexed(String field) { - return isConfigationSet(Config.INDEXED, field); + public boolean isIndexed(FieldName field) { + return isConfigationSet(Config.INDEXED, field.string()); } @Override - public boolean hasDocValues(String field) { - return isConfigationSet(Config.DOC_VALUES, field); + public boolean hasDocValues(FieldName field) { + return isConfigationSet(Config.DOC_VALUES, field.string()); } @Override - public boolean hasExactSubfield(String field) { - return isConfigationSet(Config.EXACT_SUBFIELD, field); + public boolean hasExactSubfield(FieldName field) { + return isConfigationSet(Config.EXACT_SUBFIELD, field.string()); } @Override @@ -500,8 +501,8 @@ private static SearchStats fieldMatchingExistOrMissing(boolean exists, String... private final Set fields = Set.of(names); @Override - public boolean exists(String field) { - return fields.contains(field) == exists; + public boolean exists(FieldName field) { + return fields.contains(field.string()) == exists; } }; } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/union_types.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/union_types.csv-spec index ce933bd6eba7f..7c89864989b08 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/union_types.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/union_types.csv-spec @@ -1325,6 +1325,19 @@ count:long | message:keyword 3 | Connected to 10.1.0.3 ; +multiIndexStatsOfMultiTypedField +required_capability: union_types +required_capability: casting_operator +required_capability: union_types_numeric_widening + +FROM apps, apps_short +| STATS s = sum(id::integer) +; + +s:long +210 +; + multiIndexMultiColumnTypesRename required_capability: union_types required_capability: index_metadata_field diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index a4fdb108e639d..7faab1493096a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -1744,7 +1744,7 @@ private Expression resolveConvertFunction(ConvertFunction convert, List, List> pushableStats( if (target instanceof FieldAttribute fa) { var fName = fa.fieldName(); if (context.searchStats().isSingleValue(fName)) { - fieldName = fName; + fieldName = fName.string(); query = QueryBuilders.existsQuery(fieldName); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java index 1c2bbe36db9ea..0b2fafcf2df2e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java @@ -152,7 +152,7 @@ public final PhysicalOperation fieldExtractPhysicalOperation(FieldExtractExec fi private static String getFieldName(Attribute attr) { // Do not use the field attribute name, this can deviate from the field name for union types. - return attr instanceof FieldAttribute fa ? fa.fieldName() : attr.name(); + return attr instanceof FieldAttribute fa ? fa.fieldName().string() : attr.name(); } private BlockLoader getBlockLoaderFor(int shardId, Attribute attr, MappedFieldType.FieldExtractPreference fieldExtractPreference) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchContextStats.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchContextStats.java index 8cb10d14bb578..c9766bad1c602 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchContextStats.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchContextStats.java @@ -27,6 +27,8 @@ import org.elasticsearch.index.mapper.TextFieldMapper; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute.FieldName; import org.elasticsearch.xpack.esql.core.type.DataType; import java.io.IOException; @@ -126,21 +128,25 @@ private boolean fastNoCacheFieldExists(String field) { return false; } - public boolean exists(String field) { - var stat = cache.get(field); - return stat != null ? stat.config.exists : fastNoCacheFieldExists(field); + @Override + public boolean exists(FieldName field) { + var stat = cache.get(field.string()); + return stat != null ? stat.config.exists : fastNoCacheFieldExists(field.string()); } - public boolean isIndexed(String field) { - return cache.computeIfAbsent(field, this::makeFieldStats).config.indexed; + @Override + public boolean isIndexed(FieldName field) { + return cache.computeIfAbsent(field.string(), this::makeFieldStats).config.indexed; } - public boolean hasDocValues(String field) { - return cache.computeIfAbsent(field, this::makeFieldStats).config.hasDocValues; + @Override + public boolean hasDocValues(FieldName field) { + return cache.computeIfAbsent(field.string(), this::makeFieldStats).config.hasDocValues; } - public boolean hasExactSubfield(String field) { - return cache.computeIfAbsent(field, this::makeFieldStats).config.hasExactSubfield; + @Override + public boolean hasExactSubfield(FieldName field) { + return cache.computeIfAbsent(field.string(), this::makeFieldStats).config.hasExactSubfield; } public long count() { @@ -152,12 +158,13 @@ public long count() { return completed ? count[0] : -1; } - public long count(String field) { - var stat = cache.computeIfAbsent(field, this::makeFieldStats); + @Override + public long count(FieldName field) { + var stat = cache.computeIfAbsent(field.string(), this::makeFieldStats); if (stat.count == null) { var count = new long[] { 0 }; boolean completed = doWithContexts(r -> { - count[0] += countEntries(r, field); + count[0] += countEntries(r, field.string()); return true; }, false); stat.count = completed ? count[0] : -1; @@ -165,9 +172,10 @@ public long count(String field) { return stat.count; } - public long count(String field, BytesRef value) { + @Override + public long count(FieldName field, BytesRef value) { var count = new long[] { 0 }; - Term term = new Term(field, value); + Term term = new Term(field.string(), value); boolean completed = doWithContexts(r -> { count[0] += r.docFreq(term); return true; @@ -175,12 +183,13 @@ public long count(String field, BytesRef value) { return completed ? count[0] : -1; } - public byte[] min(String field, DataType dataType) { - var stat = cache.computeIfAbsent(field, this::makeFieldStats); + @Override + public byte[] min(FieldName field, DataType dataType) { + var stat = cache.computeIfAbsent(field.string(), this::makeFieldStats); if (stat.min == null) { var min = new byte[][] { null }; doWithContexts(r -> { - byte[] localMin = PointValues.getMinPackedValue(r, field); + byte[] localMin = PointValues.getMinPackedValue(r, field.string()); // TODO: how to compare with the previous min if (localMin != null) { if (min[0] == null) { @@ -197,12 +206,13 @@ public byte[] min(String field, DataType dataType) { return null; } - public byte[] max(String field, DataType dataType) { - var stat = cache.computeIfAbsent(field, this::makeFieldStats); + @Override + public byte[] max(FieldName field, DataType dataType) { + var stat = cache.computeIfAbsent(field.string(), this::makeFieldStats); if (stat.max == null) { var max = new byte[][] { null }; doWithContexts(r -> { - byte[] localMax = PointValues.getMaxPackedValue(r, field); + byte[] localMax = PointValues.getMaxPackedValue(r, field.string()); // TODO: how to compare with the previous max if (localMax != null) { if (max[0] == null) { @@ -219,8 +229,10 @@ public byte[] max(String field, DataType dataType) { return null; } - public boolean isSingleValue(String field) { - var stat = cache.computeIfAbsent(field, this::makeFieldStats); + @Override + public boolean isSingleValue(FieldName field) { + String fieldName = field.string(); + var stat = cache.computeIfAbsent(fieldName, this::makeFieldStats); if (stat.singleValue == null) { // there's no such field so no need to worry about multi-value fields if (stat.config.exists == false) { @@ -229,11 +241,11 @@ public boolean isSingleValue(String field) { // fields are MV per default var sv = new boolean[] { false }; for (SearchExecutionContext context : contexts) { - MappedFieldType mappedType = context.isFieldMapped(field) ? context.getFieldType(field) : null; + MappedFieldType mappedType = context.isFieldMapped(fieldName) ? context.getFieldType(fieldName) : null; if (mappedType != null) { sv[0] = true; doWithContexts(r -> { - sv[0] &= detectSingleValue(r, mappedType, field); + sv[0] &= detectSingleValue(r, mappedType, fieldName); return sv[0]; }, true); break; @@ -293,9 +305,9 @@ private boolean detectSingleValue(IndexReader r, MappedFieldType fieldType, Stri } @Override - public boolean canUseEqualityOnSyntheticSourceDelegate(String name, String value) { + public boolean canUseEqualityOnSyntheticSourceDelegate(FieldAttribute.FieldName name, String value) { for (SearchExecutionContext ctx : contexts) { - MappedFieldType type = ctx.getFieldType(name); + MappedFieldType type = ctx.getFieldType(name.string()); if (type == null) { return false; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchStats.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchStats.java index 748ed826836e7..ff1701104eca9 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchStats.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/stats/SearchStats.java @@ -8,6 +8,8 @@ package org.elasticsearch.xpack.esql.stats; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute.FieldName; import org.elasticsearch.xpack.esql.core.type.DataType; /** @@ -17,33 +19,33 @@ public interface SearchStats { SearchStats EMPTY = new EmptySearchStats(); - boolean exists(String field); + boolean exists(FieldName field); - boolean isIndexed(String field); + boolean isIndexed(FieldName field); - boolean hasDocValues(String field); + boolean hasDocValues(FieldName field); - boolean hasExactSubfield(String field); + boolean hasExactSubfield(FieldName field); long count(); - long count(String field); + long count(FieldName field); - long count(String field, BytesRef value); + long count(FieldName field, BytesRef value); - byte[] min(String field, DataType dataType); + byte[] min(FieldName field, DataType dataType); - byte[] max(String field, DataType dataType); + byte[] max(FieldName field, DataType dataType); - boolean isSingleValue(String field); + boolean isSingleValue(FieldName field); - boolean canUseEqualityOnSyntheticSourceDelegate(String name, String value); + boolean canUseEqualityOnSyntheticSourceDelegate(FieldName name, String value); /** * Returns the value for a field if it's a constant (eg. a constant_keyword with only one value for the involved indices). * NULL if the field is not a constant. */ - default String constantValue(String name) { + default String constantValue(FieldAttribute.FieldName name) { return null; } @@ -53,22 +55,22 @@ default String constantValue(String name) { record EmptySearchStats() implements SearchStats { @Override - public boolean exists(String field) { + public boolean exists(FieldName field) { return false; } @Override - public boolean isIndexed(String field) { + public boolean isIndexed(FieldName field) { return false; } @Override - public boolean hasDocValues(String field) { + public boolean hasDocValues(FieldName field) { return false; } @Override - public boolean hasExactSubfield(String field) { + public boolean hasExactSubfield(FieldName field) { return false; } @@ -78,32 +80,32 @@ public long count() { } @Override - public long count(String field) { + public long count(FieldName field) { return 0; } @Override - public long count(String field, BytesRef value) { + public long count(FieldName field, BytesRef value) { return 0; } @Override - public byte[] min(String field, DataType dataType) { + public byte[] min(FieldName field, DataType dataType) { return null; } @Override - public byte[] max(String field, DataType dataType) { + public byte[] max(FieldName field, DataType dataType) { return null; } @Override - public boolean isSingleValue(String field) { + public boolean isSingleValue(FieldName field) { return true; } @Override - public boolean canUseEqualityOnSyntheticSourceDelegate(String name, String value) { + public boolean canUseEqualityOnSyntheticSourceDelegate(FieldName name, String value) { return false; } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java index 0901ff3d15a91..8251611dcfe47 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java @@ -28,6 +28,7 @@ import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.EsField; +import org.elasticsearch.xpack.esql.core.type.InvalidMappedField; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case; import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce; @@ -41,6 +42,7 @@ import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferIsNotNull; import org.elasticsearch.xpack.esql.parser.EsqlParser; +import org.elasticsearch.xpack.esql.plan.logical.Aggregate; import org.elasticsearch.xpack.esql.plan.logical.EsRelation; import org.elasticsearch.xpack.esql.plan.logical.Eval; import org.elasticsearch.xpack.esql.plan.logical.Filter; @@ -60,6 +62,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import static java.util.Collections.emptyMap; import static org.elasticsearch.xpack.esql.EsqlTestUtils.L; @@ -650,7 +653,7 @@ public void testReplaceUpperStringCasinqgWithInsensitiveRLike() { var filter = as(limit.child(), Filter.class); var rlike = as(filter.condition(), RLike.class); var field = as(rlike.field(), FieldAttribute.class); - assertThat(field.fieldName(), is("first_name")); + assertThat(field.fieldName().string(), is("first_name")); assertThat(rlike.pattern().pattern(), is("VALÜ*")); assertThat(rlike.caseInsensitive(), is(true)); var source = as(filter.child(), EsRelation.class); @@ -664,7 +667,7 @@ public void testReplaceLowerStringCasingWithInsensitiveRLike() { var filter = as(limit.child(), Filter.class); var rlike = as(filter.condition(), RLike.class); var field = as(rlike.field(), FieldAttribute.class); - assertThat(field.fieldName(), is("first_name")); + assertThat(field.fieldName().string(), is("first_name")); assertThat(rlike.pattern().pattern(), is("valü*")); assertThat(rlike.caseInsensitive(), is(true)); var source = as(filter.child(), EsRelation.class); @@ -689,7 +692,7 @@ public void testReplaceUpperStringCasingWithInsensitiveLike() { var filter = as(limit.child(), Filter.class); var wlike = as(filter.condition(), WildcardLike.class); var field = as(wlike.field(), FieldAttribute.class); - assertThat(field.fieldName(), is("first_name")); + assertThat(field.fieldName().string(), is("first_name")); assertThat(wlike.pattern().pattern(), is("VALÜ*")); assertThat(wlike.caseInsensitive(), is(true)); var source = as(filter.child(), EsRelation.class); @@ -703,7 +706,7 @@ public void testReplaceLowerStringCasingWithInsensitiveLike() { var filter = as(limit.child(), Filter.class); var wlike = as(filter.condition(), WildcardLike.class); var field = as(wlike.field(), FieldAttribute.class); - assertThat(field.fieldName(), is("first_name")); + assertThat(field.fieldName().string(), is("first_name")); assertThat(wlike.pattern().pattern(), is("valü*")); assertThat(wlike.caseInsensitive(), is(true)); var source = as(filter.child(), EsRelation.class); @@ -720,6 +723,27 @@ public void testReplaceStringCasingAndLikeWithLocalRelation() { assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY)); } + /** + * Limit[1000[INTEGER],false] + * \_Aggregate[[],[SUM($$integer_long_field$converted_to$long{f$}#5,true[BOOLEAN]) AS sum(integer_long_field::long)#3]] + * \_Filter[ISNOTNULL($$integer_long_field$converted_to$long{f$}#5)] + * \_EsRelation[test*][!integer_long_field, $$integer_long_field$converted..] + */ + public void testUnionTypesInferNonNullAggConstraint() { + LogicalPlan coordinatorOptimized = plan("FROM test* | STATS sum(integer_long_field::long)", analyzerWithUnionTypeMapping()); + var plan = localPlan(coordinatorOptimized, TEST_SEARCH_STATS); + + var limit = asLimit(plan, 1000); + var agg = as(limit.child(), Aggregate.class); + var filter = as(agg.child(), Filter.class); + var relation = as(filter.child(), EsRelation.class); + + var isNotNull = as(filter.condition(), IsNotNull.class); + var unionTypeField = as(isNotNull.field(), FieldAttribute.class); + assertEquals("$$integer_long_field$converted_to$long", unionTypeField.name()); + assertEquals("integer_long_field", unionTypeField.fieldName().string()); + } + private IsNotNull isNotNull(Expression field) { return new IsNotNull(EMPTY, field); } @@ -730,7 +754,7 @@ private LocalRelation asEmptyRelation(Object o) { return empty; } - private LogicalPlan plan(String query) { + private LogicalPlan plan(String query, Analyzer analyzer) { var analyzed = analyzer.analyze(parser.createStatement(query)); // System.out.println(analyzed); var optimized = logicalOptimizer.optimize(analyzed); @@ -738,6 +762,10 @@ private LogicalPlan plan(String query) { return optimized; } + private LogicalPlan plan(String query) { + return plan(query, analyzer); + } + private LogicalPlan localPlan(LogicalPlan plan, SearchStats searchStats) { var localContext = new LocalLogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small(), searchStats); // System.out.println(plan); @@ -750,6 +778,31 @@ private LogicalPlan localPlan(String query) { return localPlan(plan(query), TEST_SEARCH_STATS); } + private static Analyzer analyzerWithUnionTypeMapping() { + InvalidMappedField unionTypeField = new InvalidMappedField( + "integer_long_field", + Map.of("integer", Set.of("test1"), "long", Set.of("test2")) + ); + + EsIndex test = new EsIndex( + "test*", + Map.of("integer_long_field", unionTypeField), + Map.of("test1", IndexMode.STANDARD, "test2", IndexMode.STANDARD) + ); + IndexResolution getIndexResult = IndexResolution.valid(test); + + return new Analyzer( + new AnalyzerContext( + EsqlTestUtils.TEST_CFG, + new EsqlFunctionRegistry(), + getIndexResult, + emptyPolicyResolution(), + emptyInferenceResolution() + ), + TEST_VERIFIER + ); + } + @Override protected List filteredWarnings() { return withDefaultLimitWarning(super.filteredWarnings()); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java index b0f16b384a489..047d5a54976d8 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java @@ -165,20 +165,20 @@ public class LocalPhysicalPlanOptimizerTests extends MapperServiceTestCase { private final Configuration config; private final SearchStats IS_SV_STATS = new TestSearchStats() { @Override - public boolean isSingleValue(String field) { + public boolean isSingleValue(FieldAttribute.FieldName field) { return true; } }; private final SearchStats CONSTANT_K_STATS = new TestSearchStats() { @Override - public boolean isSingleValue(String field) { + public boolean isSingleValue(FieldAttribute.FieldName field) { return true; } @Override - public String constantValue(String name) { - return name.startsWith("constant_keyword") ? "foo" : null; + public String constantValue(FieldAttribute.FieldName name) { + return name.string().startsWith("constant_keyword") ? "foo" : null; } }; @@ -565,8 +565,8 @@ public void testCountFieldsAndAllWithFilter() { public void testLocalAggOptimizedToLocalRelation() { var stats = new TestSearchStats() { @Override - public boolean exists(String field) { - return "emp_no".equals(field) == false; + public boolean exists(FieldAttribute.FieldName field) { + return "emp_no".equals(field.string()) == false; } }; @@ -2024,10 +2024,10 @@ public void testToDateNanosPushDown() { assertEquals(2, projections.size()); FieldAttribute fa = as(projections.get(0), FieldAttribute.class); assertEquals(DATE_NANOS, fa.dataType()); - assertEquals("date_and_date_nanos", fa.fieldName()); + assertEquals("date_and_date_nanos", fa.fieldName().string()); assertTrue(isMultiTypeEsField(fa)); // mixed date and date_nanos are auto-casted UnsupportedAttribute ua = as(projections.get(1), UnsupportedAttribute.class); // mixed date, date_nanos and long are not auto-casted - assertEquals("date_and_date_nanos_and_long", ua.fieldName()); + assertEquals("date_and_date_nanos_and_long", ua.fieldName().string()); var limit = as(project.child(), LimitExec.class); var exchange = as(limit.child(), ExchangeExec.class); project = as(exchange.child(), ProjectExec.class); @@ -2038,7 +2038,7 @@ public void testToDateNanosPushDown() { GreaterThanOrEqual gt = as(filter.condition(), GreaterThanOrEqual.class); fa = as(gt.left(), FieldAttribute.class); assertTrue(isMultiTypeEsField(fa)); - assertEquals("date_and_date_nanos_and_long", fa.fieldName()); + assertEquals("date_and_date_nanos_and_long", fa.fieldName().string()); fieldExtract = as(filter.child(), FieldExtractExec.class); // extract date_and_date_nanos_and_long var esQuery = as(fieldExtract.child(), EsQueryExec.class); var source = ((SingleValueQuery.Builder) esQuery.query()).source(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java index 926fdd75cac6a..6d66a187e6168 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java @@ -6341,7 +6341,7 @@ public void testReplaceStringCasingWithInsensitiveEqualsUnwrap() { var filter = as(limit.child(), Filter.class); var insensitive = as(filter.condition(), InsensitiveEquals.class); var field = as(insensitive.left(), FieldAttribute.class); - assertThat(field.fieldName(), is("first_name")); + assertThat(field.fieldName().string(), is("first_name")); var bRef = as(insensitive.right().fold(FoldContext.small()), BytesRef.class); assertThat(bRef.utf8ToString(), is("VALÜ")); as(filter.child(), EsRelation.class); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java index cbca9b3eaae88..26a2de5b0c8dc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java @@ -988,12 +988,12 @@ public void testPushAndInequalitiesFilter() { var bq = as(source.query(), BoolQueryBuilder.class); assertThat(bq.must(), hasSize(2)); var first = as(sv(bq.must().get(0), "emp_no"), RangeQueryBuilder.class); - assertThat(first.fieldName(), equalTo("emp_no")); + assertThat(first.fieldName().toString(), equalTo("emp_no")); assertThat(first.from(), equalTo(-1)); assertThat(first.includeLower(), equalTo(false)); assertThat(first.to(), nullValue()); var second = as(sv(bq.must().get(1), "salary"), RangeQueryBuilder.class); - assertThat(second.fieldName(), equalTo("salary")); + assertThat(second.fieldName().toString(), equalTo("salary")); assertThat(second.from(), nullValue()); assertThat(second.to(), equalTo(10)); assertThat(second.includeUpper(), equalTo(false)); @@ -2442,7 +2442,7 @@ public void testPushDownEvalFilter() { assertThat(range2.fieldName(), is("first_name")); var sort = source.sorts(); assertThat(sort.size(), is(1)); - assertThat(sort.get(0).field().fieldName(), is("first_name")); + assertThat(sort.get(0).field().fieldName().string(), is("first_name")); } /** @@ -2501,7 +2501,7 @@ public void testPushDownEvalSwapFilter() { assertThat(exists.fieldName(), is("first_name")); var sort = source.sorts(); assertThat(sort.size(), is(1)); - assertThat(sort.get(0).field().fieldName(), is("last_name")); + assertThat(sort.get(0).field().fieldName().string(), is("last_name")); } /** @@ -3223,8 +3223,9 @@ public void testAggToLocalRelationOnDataNode() { """); var stats = new EsqlTestUtils.TestSearchStats() { - public boolean exists(String field) { - return "salary".equals(field); + @Override + public boolean exists(FieldAttribute.FieldName field) { + return "salary".equals(field.string()); } }; var optimized = optimizedPlan(plan, stats); @@ -5092,7 +5093,7 @@ public void testPushSpatialDistanceEvalToSource() { assertThat(alias.name(), is("distance")); var stDistance = as(alias.child(), StDistance.class); var location = as(stDistance.left(), FieldAttribute.class); - assertThat(location.fieldName(), is("location")); + assertThat(location.fieldName().string(), is("location")); // Validate the filter condition var and = as(filter.condition(), And.class); @@ -5146,7 +5147,7 @@ public void testPushSpatialDistanceMultiEvalToSource() { assertThat(alias2.name(), is("distance")); var stDistance = as(alias2.child(), StDistance.class); var location = as(stDistance.left(), FieldAttribute.class); - assertThat(location.fieldName(), is("location")); + assertThat(location.fieldName().string(), is("location")); var poiRef = as(stDistance.right(), Literal.class); assertThat(poiRef.value(), instanceOf(BytesRef.class)); assertThat(poiRef.value().toString(), is(poi.value().toString())); @@ -6502,7 +6503,7 @@ public void testPushCompoundTopNDistanceWithCompoundFilterAndCompoundEvalToSourc assertThat(alias2.name(), is("distance")); var stDistance = as(alias2.child(), StDistance.class); var location = as(stDistance.left(), FieldAttribute.class); - assertThat(location.fieldName(), is("location")); + assertThat(location.fieldName().string(), is("location")); var poiRef = as(stDistance.right(), Literal.class); assertThat(poiRef.value(), instanceOf(BytesRef.class)); assertThat(poiRef.value().toString(), is(poi.value().toString())); @@ -6688,7 +6689,7 @@ public void testPushCompoundTopNDistanceWithCompoundFilterAndNestedCompoundEvalT assertThat(alias2.name(), is("distance")); var stDistance = as(alias2.child(), StDistance.class); var location = as(stDistance.left(), FieldAttribute.class); - assertThat(location.fieldName(), is("location")); + assertThat(location.fieldName().string(), is("location")); var poiRef = as(stDistance.right(), Literal.class); assertThat(poiRef.value(), instanceOf(BytesRef.class)); assertThat(poiRef.value().toString(), is(poi.value().toString())); @@ -8282,12 +8283,12 @@ protected List filteredWarnings() { private static final SearchStats SEARCH_STATS_SHORT_DELEGATES = new EsqlTestUtils.TestSearchStats() { @Override - public boolean hasExactSubfield(String field) { + public boolean hasExactSubfield(FieldAttribute.FieldName field) { return false; } @Override - public boolean canUseEqualityOnSyntheticSourceDelegate(String name, String value) { + public boolean canUseEqualityOnSyntheticSourceDelegate(FieldAttribute.FieldName name, String value) { return value.length() < 4; } }; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java index 71f2215bb0f58..cacf6e422882b 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushTopNToSourceTests.java @@ -451,7 +451,7 @@ private static void assertPushdownSort( String name = ((Attribute) expectedSorts.get(i).child()).name(); EsQueryExec.Sort sort = sorts.get(i); if (sort.field() != null) { - String fieldName = sort.field().fieldName(); + String fieldName = sort.field().fieldName().string(); assertThat("Expect sort[" + i + "] name to match", fieldName, is(sortName(name, fieldMap))); } assertThat("Expect sort[" + i + "] direction to match", sort.direction(), is(expectedSorts.get(i).direction())); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/TestPhysicalOperationProviders.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/TestPhysicalOperationProviders.java index 2e3d6482440dd..d5aa1af7feec2 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/TestPhysicalOperationProviders.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/TestPhysicalOperationProviders.java @@ -328,7 +328,7 @@ private Block getBlockForMultiType(DocBlock indexDoc, MultiTypeEsField multiType if (conversion == null) { return getNullsBlock(indexDoc); } - return switch (extractBlockForSingleDoc(indexDoc, ((FieldAttribute) conversion.field()).fieldName(), blockCopier)) { + return switch (extractBlockForSingleDoc(indexDoc, ((FieldAttribute) conversion.field()).fieldName().string(), blockCopier)) { case BlockResultMissing unused -> getNullsBlock(indexDoc); case BlockResultSuccess success -> TypeConverter.fromConvertFunction(conversion).convert(success.block); }; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/DisabledSearchStats.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/DisabledSearchStats.java index 60848c4ae5d00..6d8f5ca925121 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/DisabledSearchStats.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/stats/DisabledSearchStats.java @@ -8,27 +8,28 @@ package org.elasticsearch.xpack.esql.stats; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute.FieldName; import org.elasticsearch.xpack.esql.core.type.DataType; public class DisabledSearchStats implements SearchStats { @Override - public boolean exists(String field) { + public boolean exists(FieldName field) { return true; } @Override - public boolean isIndexed(String field) { + public boolean isIndexed(FieldName field) { return true; } @Override - public boolean hasDocValues(String field) { + public boolean hasDocValues(FieldName field) { return true; } @Override - public boolean hasExactSubfield(String field) { + public boolean hasExactSubfield(FieldName field) { return true; } @@ -38,32 +39,32 @@ public long count() { } @Override - public long count(String field) { + public long count(FieldName field) { return -1; } @Override - public long count(String field, BytesRef value) { + public long count(FieldName field, BytesRef value) { return -1; } @Override - public byte[] min(String field, DataType dataType) { + public byte[] min(FieldName field, DataType dataType) { return null; } @Override - public byte[] max(String field, DataType dataType) { + public byte[] max(FieldName field, DataType dataType) { return null; } @Override - public boolean isSingleValue(String field) { + public boolean isSingleValue(FieldName field) { return false; } @Override - public boolean canUseEqualityOnSyntheticSourceDelegate(String name, String value) { + public boolean canUseEqualityOnSyntheticSourceDelegate(FieldName name, String value) { return false; } } From 4205dc50525e51648f67dfd1311c57a54030a080 Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:40:17 +0200 Subject: [PATCH 065/102] Remove usages of `Metadata.Builder#indexGraveyard` (#129041) And replace it with appropriate calls to the equivalent method on `ProjectMetadata.Builder`. In most cases this _does not_ make the code project aware, but does reduce the number of deprecated methods in use. Concerns both the getter and the setter. --- .../TransportDeleteDanglingIndexAction.java | 19 ++++++--------- .../cluster/metadata/Metadata.java | 11 --------- .../cluster/ClusterChangedEventTests.java | 12 ++++++---- .../ElasticsearchNodeCommandTests.java | 13 ++++++---- .../cluster/metadata/MetadataTests.java | 24 +++++++++++++------ .../gateway/DanglingIndicesStateTests.java | 6 ++++- .../indices/IndicesServiceTests.java | 6 +++-- 7 files changed, 49 insertions(+), 42 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/dangling/delete/TransportDeleteDanglingIndexAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/dangling/delete/TransportDeleteDanglingIndexAction.java index 3d9ace9635333..5fb629957b286 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/dangling/delete/TransportDeleteDanglingIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/dangling/delete/TransportDeleteDanglingIndexAction.java @@ -29,7 +29,7 @@ import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.metadata.IndexGraveyard; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.SuppressForbidden; @@ -126,9 +126,9 @@ private void submitUnbatchedTask(@SuppressWarnings("SameParameterValue") String } private ClusterState deleteDanglingIndex(ClusterState currentState, Index indexToDelete) { - final Metadata metaData = currentState.getMetadata(); + final var project = currentState.metadata().getProject(); - for (Map.Entry each : metaData.getProject().indices().entrySet()) { + for (Map.Entry each : project.indices().entrySet()) { if (indexToDelete.getUUID().equals(each.getValue().getIndexUUID())) { throw new IllegalArgumentException( "Refusing to delete dangling index " @@ -143,18 +143,13 @@ private ClusterState deleteDanglingIndex(ClusterState currentState, Index indexT // By definition, a dangling index is an index not present in the cluster state and with no tombstone, // so we shouldn't reach this point if these conditions aren't met. For super-safety, however, check // that a tombstone doesn't already exist for this index. - if (metaData.getProject().indexGraveyard().containsIndex(indexToDelete)) { + if (project.indexGraveyard().containsIndex(indexToDelete)) { return currentState; } - Metadata.Builder metaDataBuilder = Metadata.builder(metaData); - - final IndexGraveyard newGraveyard = IndexGraveyard.builder(metaDataBuilder.indexGraveyard()) - .addTombstone(indexToDelete) - .build(settings); - metaDataBuilder.indexGraveyard(newGraveyard); - - return ClusterState.builder(currentState).metadata(metaDataBuilder.build()).build(); + final IndexGraveyard newGraveyard = IndexGraveyard.builder(project.indexGraveyard()).addTombstone(indexToDelete).build(settings); + final ProjectMetadata updatedProject = ProjectMetadata.builder(project).indexGraveyard(newGraveyard).build(); + return ClusterState.builder(currentState).putProjectMetadata(updatedProject).build(); } @Override diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java index dc55f4516d252..5309bff27f8c0 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java @@ -1780,17 +1780,6 @@ public Builder removeReservedState(ReservedStateMetadata metadata) { return this; } - @Deprecated(forRemoval = true) - public Builder indexGraveyard(final IndexGraveyard indexGraveyard) { - getSingleProject().indexGraveyard(indexGraveyard); - return this; - } - - @Deprecated(forRemoval = true) - public IndexGraveyard indexGraveyard() { - return getSingleProject().indexGraveyard(); - } - @Deprecated(forRemoval = true) public Builder updateSettings(Settings settings, String... indices) { getSingleProject().updateSettings(settings, indices); diff --git a/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java b/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java index ed5f6cf3ca411..ea83c92bfcdd6 100644 --- a/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/ClusterChangedEventTests.java @@ -702,20 +702,24 @@ private static ClusterState nextState( final ClusterState.Builder builder = ClusterState.builder(previousState); builder.stateUUID(UUIDs.randomBase64UUID()); final Metadata.Builder metaBuilder = Metadata.builder(previousState.metadata()); + // The refactorings required to pass an explicit project ID to this method (and the state creation methods) is not worth it. + final var previousProject = previousState.metadata().projects().values().iterator().next(); + final ProjectMetadata.Builder projectBuilder = ProjectMetadata.builder(previousProject); if (changeClusterUUID || addedIndices.size() > 0 || deletedIndices.size() > 0) { // there is some change in metadata cluster state if (changeClusterUUID) { metaBuilder.clusterUUID(UUIDs.randomBase64UUID()); } for (Index index : addedIndices) { - metaBuilder.put(createIndexMetadata(index), true); + projectBuilder.put(createIndexMetadata(index), true); } for (Index index : deletedIndices) { - metaBuilder.remove(index.getName()); - IndexGraveyard.Builder graveyardBuilder = IndexGraveyard.builder(metaBuilder.indexGraveyard()); + projectBuilder.remove(index.getName()); + IndexGraveyard.Builder graveyardBuilder = IndexGraveyard.builder(projectBuilder.indexGraveyard()); graveyardBuilder.addTombstone(index); - metaBuilder.indexGraveyard(graveyardBuilder.build()); + projectBuilder.indexGraveyard(graveyardBuilder.build()); } + metaBuilder.put(projectBuilder); builder.metadata(metaBuilder); } if (numNodesToRemove > 0) { diff --git a/server/src/test/java/org/elasticsearch/cluster/coordination/ElasticsearchNodeCommandTests.java b/server/src/test/java/org/elasticsearch/cluster/coordination/ElasticsearchNodeCommandTests.java index 9a01f44fa067f..f51debe8a5b24 100644 --- a/server/src/test/java/org/elasticsearch/cluster/coordination/ElasticsearchNodeCommandTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/coordination/ElasticsearchNodeCommandTests.java @@ -13,8 +13,11 @@ import org.elasticsearch.cluster.metadata.IndexGraveyard; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectId; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.index.Index; import org.elasticsearch.indices.IndicesModule; import org.elasticsearch.test.ESTestCase; @@ -93,8 +96,8 @@ private void runLoadStateTest(boolean hasMissingCustoms, boolean preserveUnknown } private Metadata randomMeta() { - Metadata.Builder mdBuilder = Metadata.builder(); - mdBuilder.generateClusterUuidIfNeeded(); + @FixForMultiProject // Pass random project ID when usages are namespaced. + ProjectMetadata.Builder projectBuilder = ProjectMetadata.builder(ProjectId.DEFAULT); int numDelIndices = randomIntBetween(0, 5); final IndexGraveyard.Builder graveyard = IndexGraveyard.builder(); for (int i = 0; i < numDelIndices; i++) { @@ -105,11 +108,11 @@ private Metadata randomMeta() { for (int i = 0; i < numDataStreams; i++) { String dataStreamName = "name" + 1; IndexMetadata backingIndex = createFirstBackingIndex(dataStreamName).build(); - mdBuilder.put(newInstance(dataStreamName, List.of(backingIndex.getIndex()))); + projectBuilder.put(newInstance(dataStreamName, List.of(backingIndex.getIndex()))); } } - mdBuilder.indexGraveyard(graveyard.build()); - return mdBuilder.build(); + projectBuilder.indexGraveyard(graveyard.build()); + return Metadata.builder().generateClusterUuidIfNeeded().put(projectBuilder).build(); } @Override diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java index b36b9de3dab8a..b32f290f0f3c7 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/MetadataTests.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.xcontent.ChunkedToXContent; import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Predicates; import org.elasticsearch.core.SuppressForbidden; @@ -607,27 +608,35 @@ public void testUnknownFieldIndexMetadata() throws IOException { } public void testMetadataGlobalStateChangesOnIndexDeletions() { + final var projectId = randomProjectIdOrDefault(); IndexGraveyard.Builder builder = IndexGraveyard.builder(); builder.addTombstone(new Index("idx1", UUIDs.randomBase64UUID())); - final Metadata metadata1 = Metadata.builder().indexGraveyard(builder.build()).build(); - builder = IndexGraveyard.builder(metadata1.getProject().indexGraveyard()); + final Metadata metadata1 = Metadata.builder().put(ProjectMetadata.builder(projectId).indexGraveyard(builder.build())).build(); + builder = IndexGraveyard.builder(metadata1.getProject(projectId).indexGraveyard()); builder.addTombstone(new Index("idx2", UUIDs.randomBase64UUID())); - final Metadata metadata2 = Metadata.builder(metadata1).indexGraveyard(builder.build()).build(); + final Metadata metadata2 = Metadata.builder(metadata1) + .put(ProjectMetadata.builder(metadata1.getProject(projectId)).indexGraveyard(builder.build())) + .build(); assertFalse("metadata not equal after adding index deletions", Metadata.isGlobalStateEquals(metadata1, metadata2)); final Metadata metadata3 = Metadata.builder(metadata2).build(); assertTrue("metadata equal when not adding index deletions", Metadata.isGlobalStateEquals(metadata2, metadata3)); } public void testXContentWithIndexGraveyard() throws IOException { + @FixForMultiProject // XContent serialization and parsing with a random project ID currently only works when serializing in MP mode + final var projectId = ProjectId.DEFAULT; final IndexGraveyard graveyard = IndexGraveyardTests.createRandom(); - final Metadata originalMeta = Metadata.builder().indexGraveyard(graveyard).build(); + final Metadata originalMeta = Metadata.builder().put(ProjectMetadata.builder(projectId).indexGraveyard(graveyard)).build(); final XContentBuilder builder = JsonXContent.contentBuilder(); builder.startObject(); Metadata.FORMAT.toXContent(builder, originalMeta); builder.endObject(); try (XContentParser parser = createParser(JsonXContent.jsonXContent, BytesReference.bytes(builder))) { final Metadata fromXContentMeta = Metadata.fromXContent(parser); - assertThat(fromXContentMeta.getProject().indexGraveyard(), equalTo(originalMeta.getProject().indexGraveyard())); + assertThat( + fromXContentMeta.getProject(projectId).indexGraveyard(), + equalTo(originalMeta.getProject(projectId).indexGraveyard()) + ); } } @@ -975,15 +984,16 @@ public void testGlobalStateEqualsCoordinationMetadata() { } public void testSerializationWithIndexGraveyard() throws IOException { + final var projectId = randomProjectIdOrDefault(); final IndexGraveyard graveyard = IndexGraveyardTests.createRandom(); - final Metadata originalMeta = Metadata.builder().indexGraveyard(graveyard).build(); + final Metadata originalMeta = Metadata.builder().put(ProjectMetadata.builder(projectId).indexGraveyard(graveyard)).build(); final BytesStreamOutput out = new BytesStreamOutput(); originalMeta.writeTo(out); NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(ClusterModule.getNamedWriteables()); final Metadata fromStreamMeta = Metadata.readFrom( new NamedWriteableAwareStreamInput(out.bytes().streamInput(), namedWriteableRegistry) ); - assertThat(fromStreamMeta.getProject().indexGraveyard(), equalTo(fromStreamMeta.getProject().indexGraveyard())); + assertThat(fromStreamMeta.getProject(projectId).indexGraveyard(), equalTo(originalMeta.getProject(projectId).indexGraveyard())); } public void testFindMappings() throws IOException { diff --git a/server/src/test/java/org/elasticsearch/gateway/DanglingIndicesStateTests.java b/server/src/test/java/org/elasticsearch/gateway/DanglingIndicesStateTests.java index 9ac96378ba8c2..f426f4cff626d 100644 --- a/server/src/test/java/org/elasticsearch/gateway/DanglingIndicesStateTests.java +++ b/server/src/test/java/org/elasticsearch/gateway/DanglingIndicesStateTests.java @@ -13,8 +13,11 @@ import org.elasticsearch.cluster.metadata.IndexGraveyard; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectId; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexVersion; @@ -85,7 +88,8 @@ public void testDanglingIndicesNotReportedWhenTombstonePresent() throws Exceptio MetaStateWriterUtils.writeIndex(env, "test_write", dangledIndex); final IndexGraveyard graveyard = IndexGraveyard.builder().addTombstone(dangledIndex.getIndex()).build(); - final Metadata metadata = Metadata.builder().indexGraveyard(graveyard).build(); + @FixForMultiProject // Use random project ID + final Metadata metadata = Metadata.builder().put(ProjectMetadata.builder(ProjectId.DEFAULT).indexGraveyard(graveyard)).build(); DanglingIndicesState danglingState = createDanglingIndicesState(metaStateService, metadata); diff --git a/server/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java b/server/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java index 967dd3d7626b7..7f2aa62f7cf83 100644 --- a/server/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java +++ b/server/src/test/java/org/elasticsearch/indices/IndicesServiceTests.java @@ -33,6 +33,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.core.FixForMultiProject; import org.elasticsearch.core.TimeValue; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.ShardLockObtainFailedException; @@ -546,8 +547,9 @@ public void testIndexAndTombstoneWithSameNameOnStartup() throws Exception { .build(); final Index tombstonedIndex = new Index(indexName, UUIDs.randomBase64UUID()); final IndexGraveyard graveyard = IndexGraveyard.builder().addTombstone(tombstonedIndex).build(); - final Metadata metadata = Metadata.builder().put(indexMetadata, true).indexGraveyard(graveyard).build(); - final ClusterState clusterState = new ClusterState.Builder(new ClusterName("testCluster")).metadata(metadata).build(); + @FixForMultiProject // Use random project-id + final var project = ProjectMetadata.builder(ProjectId.DEFAULT).put(indexMetadata, true).indexGraveyard(graveyard).build(); + final ClusterState clusterState = new ClusterState.Builder(new ClusterName("testCluster")).putProjectMetadata(project).build(); // if all goes well, this won't throw an exception, otherwise, it will throw an IllegalStateException indicesService.verifyIndexIsDeleted(tombstonedIndex, clusterState); } From 1c6bb76d40bb2aff4f8b8754ff55d55be4139054 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:07:04 +1000 Subject: [PATCH 066/102] Mute org.elasticsearch.compute.data.sort.LongTopNSetTests testCrankyBreaker #129257 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index ed3971f523f39..ea084ec2cd594 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -520,6 +520,9 @@ tests: - class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT method: test {lookup-join.MultipleBatches* issue: https://github.com/elastic/elasticsearch/issues/129210 +- class: org.elasticsearch.compute.data.sort.LongTopNSetTests + method: testCrankyBreaker + issue: https://github.com/elastic/elasticsearch/issues/129257 # Examples: # From 3e259564093df974b28c4e541685de1d6a84cec2 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Wed, 11 Jun 2025 16:19:48 +0300 Subject: [PATCH 067/102] Enable Shard-Level Search-load rate metric (#128660) Introduces a new search load metric to the stats infrastructure, measured and tracked on a per-shard basis. The metric represents the Exponentially Weighted Moving Rate (EWMR) of search operations, calculated using the "took" time from each completed search phase. --- .../test/indices.stats/80_search_load.yml | 52 +++++ .../index/shard/IndexShardIT.java | 4 +- .../org/elasticsearch/TransportVersions.java | 1 + .../common/settings/ClusterSettings.java | 2 + .../org/elasticsearch/index/IndexModule.java | 9 +- .../org/elasticsearch/index/IndexService.java | 9 +- .../index/mapper/MapperFeatures.java | 4 +- .../index/search/stats/SearchStats.java | 35 ++- .../search/stats/SearchStatsSettings.java | 50 ++++ .../index/search/stats/ShardSearchStats.java | 24 +- .../elasticsearch/index/shard/IndexShard.java | 7 +- .../elasticsearch/indices/IndicesService.java | 9 +- .../cluster/node/stats/NodeStatsTests.java | 1 + .../elasticsearch/index/IndexModuleTests.java | 19 +- .../index/search/stats/SearchStatsTests.java | 6 +- .../search/stats/ShardSearchStatsTests.java | 216 ++++++++++++++++++ .../index/shard/IndexShardTestCase.java | 4 +- .../indices/IndexStatsMonitoringDocTests.java | 18 +- .../IndicesStatsMonitoringDocTests.java | 2 +- .../node/NodeStatsMonitoringDocTests.java | 18 +- .../xpack/security/SecurityTests.java | 4 +- .../xpack/watcher/WatcherPluginTests.java | 4 +- 22 files changed, 467 insertions(+), 31 deletions(-) create mode 100644 rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml create mode 100644 server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java create mode 100644 server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml new file mode 100644 index 0000000000000..a8c8fe46c9f34 --- /dev/null +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml @@ -0,0 +1,52 @@ +--- +setup: + - requires: + cluster_features: ["mapper.search_load_per_shard"] + reason: Shard search load stats were introduced in 9.1 +--- +"Search load is tracked at shard level": + - do: + indices.create: + index: index + body: + mappings: + properties: + name: + type: text + description: + type: text + price: + type: double + + - do: + indices.stats: + index: "index" + level: shards + metric: [ search ] + + - match: { _all.total.search.recent_search_load: 0.0 } + - match: { indices.index.total.search.recent_search_load: 0.0 } + - match: { indices.index.shards.0.0.search.recent_search_load: 0.0 } + + - do: + index: + index: index + body: { "name": "specialty coffee", "description": "arabica coffee beans", "price": 100 } + + - do: + search: + index: index + body: + query: + match: { name: "specialty coffee" } + size: 1 + + - do: + indices.stats: + index: "index" + level: shards + metric: [ search ] + + - gte: { _all.total.search.recent_search_load: 0.0 } + - gte: { indices.index.total.search.recent_search_load: 0.0 } + - gte: { indices.index.shards.0.0.search.recent_search_load: 0.0 } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java index 507724bbaeb3c..11e8b15432d35 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -53,6 +53,7 @@ import org.elasticsearch.index.flush.FlushStats; import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.translog.TestTranslog; @@ -638,7 +639,8 @@ public static final IndexShard newIndexShard( System::nanoTime, null, MapperMetrics.NOOP, - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); } diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 669952c6026e7..bca87f8dd8750 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -292,6 +292,7 @@ static TransportVersion def(int id) { public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM = def(9_092_0_00); public static final TransportVersion SNAPSHOT_INDEX_SHARD_STATUS_MISSING_STATS = def(9_093_0_00); public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK = def(9_094_0_00); + public static final TransportVersion SEARCH_LOAD_PER_INDEX_STATS = def(9_095_0_00); /* * STOP! READ THIS FIRST! No, really, 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 dea61e770b2f6..e9982b476c520 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -90,6 +90,7 @@ import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.index.engine.ThreadPoolMergeExecutorService; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.IndexingMemoryController; import org.elasticsearch.indices.IndicesQueryCache; @@ -640,6 +641,7 @@ public void apply(Settings value, Settings current, Settings previous) { ShardsAvailabilityHealthIndicatorService.REPLICA_UNASSIGNED_BUFFER_TIME, DataStreamFailureStoreSettings.DATA_STREAM_FAILURE_STORED_ENABLED_SETTING, IndexingStatsSettings.RECENT_WRITE_LOAD_HALF_LIFE_SETTING, + SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING, TransportGetAllocationStatsAction.CACHE_TTL_SETTING ); } diff --git a/server/src/main/java/org/elasticsearch/index/IndexModule.java b/server/src/main/java/org/elasticsearch/index/IndexModule.java index 3418d8a9b7b2e..42410c6b8025e 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/server/src/main/java/org/elasticsearch/index/IndexModule.java @@ -48,6 +48,7 @@ import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexingOperationListener; import org.elasticsearch.index.shard.IndexingStatsSettings; @@ -179,6 +180,7 @@ public interface DirectoryWrapper { private final SetOnce indexCommitListener = new SetOnce<>(); private final MapperMetrics mapperMetrics; private final IndexingStatsSettings indexingStatsSettings; + private final SearchStatsSettings searchStatsSettings; /** * Construct the index module for the index with the specified index settings. The index module contains extension points for plugins @@ -200,7 +202,8 @@ public IndexModule( final SlowLogFieldProvider slowLogFieldProvider, final MapperMetrics mapperMetrics, final List searchOperationListeners, - final IndexingStatsSettings indexingStatsSettings + final IndexingStatsSettings indexingStatsSettings, + final SearchStatsSettings searchStatsSettings ) { this.indexSettings = indexSettings; this.analysisRegistry = analysisRegistry; @@ -216,6 +219,7 @@ public IndexModule( this.recoveryStateFactories = recoveryStateFactories; this.mapperMetrics = mapperMetrics; this.indexingStatsSettings = indexingStatsSettings; + this.searchStatsSettings = searchStatsSettings; } /** @@ -552,7 +556,8 @@ public IndexService newIndexService( indexCommitListener.get(), mapperMetrics, queryRewriteInterceptor, - indexingStatsSettings + indexingStatsSettings, + searchStatsSettings ); success = true; return indexService; diff --git a/server/src/main/java/org/elasticsearch/index/IndexService.java b/server/src/main/java/org/elasticsearch/index/IndexService.java index d5c00294aa6b8..2c9616e0791e5 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexService.java +++ b/server/src/main/java/org/elasticsearch/index/IndexService.java @@ -67,6 +67,7 @@ import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.query.SearchIndexNameMatcher; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.GlobalCheckpointSyncer; import org.elasticsearch.index.shard.IndexEventListener; @@ -170,6 +171,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust private final MapperMetrics mapperMetrics; private final QueryRewriteInterceptor queryRewriteInterceptor; private final IndexingStatsSettings indexingStatsSettings; + private final SearchStatsSettings searchStatsSettings; @SuppressWarnings("this-escape") public IndexService( @@ -207,7 +209,8 @@ public IndexService( Engine.IndexCommitListener indexCommitListener, MapperMetrics mapperMetrics, QueryRewriteInterceptor queryRewriteInterceptor, - IndexingStatsSettings indexingStatsSettings + IndexingStatsSettings indexingStatsSettings, + SearchStatsSettings searchStatsSettings ) { super(indexSettings); assert indexCreationContext != IndexCreationContext.RELOAD_ANALYZERS @@ -293,6 +296,7 @@ public IndexService( this.retentionLeaseSyncTask = new AsyncRetentionLeaseSyncTask(this); } this.indexingStatsSettings = indexingStatsSettings; + this.searchStatsSettings = searchStatsSettings; updateFsyncTaskIfNecessary(); } @@ -583,7 +587,8 @@ public synchronized IndexShard createShard( System::nanoTime, indexCommitListener, mapperMetrics, - indexingStatsSettings + indexingStatsSettings, + searchStatsSettings ); eventListener.indexShardStateChanged(indexShard, null, indexShard.state(), "shard created"); eventListener.afterIndexShardCreated(indexShard); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java index b54c7d787cfda..cd56f281aa953 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java @@ -43,6 +43,7 @@ public class MapperFeatures implements FeatureSpecification { static final NodeFeature NPE_ON_DIMS_UPDATE_FIX = new NodeFeature("mapper.npe_on_dims_update_fix"); static final NodeFeature IVF_FORMAT_CLUSTER_FEATURE = new NodeFeature("mapper.ivf_format_cluster_feature"); static final NodeFeature IVF_NESTED_SUPPORT = new NodeFeature("mapper.ivf_nested_support"); + static final NodeFeature SEARCH_LOAD_PER_SHARD = new NodeFeature("mapper.search_load_per_shard"); @Override public Set getTestFeatures() { @@ -72,7 +73,8 @@ public Set getTestFeatures() { RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING, USE_DEFAULT_OVERSAMPLE_VALUE_FOR_BBQ, IVF_FORMAT_CLUSTER_FEATURE, - IVF_NESTED_SUPPORT + IVF_NESTED_SUPPORT, + SEARCH_LOAD_PER_SHARD ); } } diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 8b19d72ccc09d..4d6247796af59 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,6 +49,10 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; + // This tracks the search execution time across different phases (e.g., query, fetch, etc.), favouring more recent + // values by assigning them greater significance than older values. + private double recentSearchLoad; + private Stats() { // for internal use, initializes all counts to 0 } @@ -67,7 +71,8 @@ public Stats( long scrollCurrent, long suggestCount, long suggestTimeInMillis, - long suggestCurrent + long suggestCurrent, + double recentSearchLoad ) { this.queryCount = queryCount; this.queryTimeInMillis = queryTimeInMillis; @@ -86,6 +91,9 @@ public Stats( this.suggestCount = suggestCount; this.suggestTimeInMillis = suggestTimeInMillis; this.suggestCurrent = suggestCurrent; + + this.recentSearchLoad = recentSearchLoad; + } private Stats(StreamInput in) throws IOException { @@ -109,6 +117,10 @@ private Stats(StreamInput in) throws IOException { queryFailure = in.readVLong(); fetchFailure = in.readVLong(); } + + if (in.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) { + recentSearchLoad = in.readDouble(); + } } @Override @@ -133,6 +145,10 @@ public void writeTo(StreamOutput out) throws IOException { out.writeVLong(queryFailure); out.writeVLong(fetchFailure); } + + if (out.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) { + out.writeDouble(recentSearchLoad); + } } public void add(Stats stats) { @@ -153,6 +169,8 @@ public void add(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; suggestCurrent += stats.suggestCurrent; + + recentSearchLoad += stats.recentSearchLoad; } public void addForClosingShard(Stats stats) { @@ -171,6 +189,8 @@ public void addForClosingShard(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; + + recentSearchLoad += stats.recentSearchLoad; } public long getQueryCount() { @@ -245,6 +265,10 @@ public long getSuggestCurrent() { return suggestCurrent; } + public double getSearchLoadRate() { + return recentSearchLoad; + } + public static Stats readStats(StreamInput in) throws IOException { return new Stats(in); } @@ -269,6 +293,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.humanReadableField(Fields.SUGGEST_TIME_IN_MILLIS, Fields.SUGGEST_TIME, getSuggestTime()); builder.field(Fields.SUGGEST_CURRENT, suggestCurrent); + builder.field(Fields.RECENT_SEARCH_LOAD, recentSearchLoad); + return builder; } @@ -290,7 +316,8 @@ public boolean equals(Object o) { && scrollCurrent == that.scrollCurrent && suggestCount == that.suggestCount && suggestTimeInMillis == that.suggestTimeInMillis - && suggestCurrent == that.suggestCurrent; + && suggestCurrent == that.suggestCurrent + && recentSearchLoad == that.recentSearchLoad; } @Override @@ -309,7 +336,8 @@ public int hashCode() { scrollCurrent, suggestCount, suggestTimeInMillis, - suggestCurrent + suggestCurrent, + recentSearchLoad ); } } @@ -427,6 +455,7 @@ static final class Fields { static final String SUGGEST_TIME = "suggest_time"; static final String SUGGEST_TIME_IN_MILLIS = "suggest_time_in_millis"; static final String SUGGEST_CURRENT = "suggest_current"; + static final String RECENT_SEARCH_LOAD = "recent_search_load"; } @Override diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java new file mode 100644 index 0000000000000..2ab13ae60411e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java @@ -0,0 +1,50 @@ +/* + * 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.index.search.stats; + +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; + +/** + * Container for cluster settings + */ +public class SearchStatsSettings { + + public static final TimeValue RECENT_READ_LOAD_HALF_LIFE_DEFAULT = TimeValue.timeValueMinutes(5); + static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MIN = TimeValue.timeValueSeconds(1); // A sub-second half-life makes no sense + static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MAX = TimeValue.timeValueDays(100_000); // Long.MAX_VALUE nanos, rounded down + + /** + * A cluster setting giving the half-life, in seconds, to use for the Exponentially Weighted Moving Rate calculation used for the + * recency-weighted read load + * + *

This is dynamic, but changes only apply to newly-opened shards. + */ + public static final Setting RECENT_READ_LOAD_HALF_LIFE_SETTING = Setting.timeSetting( + "indices.stats.recent_read_load.half_life", + RECENT_READ_LOAD_HALF_LIFE_DEFAULT, + RECENT_READ_LOAD_HALF_LIFE_MIN, + RECENT_READ_LOAD_HALF_LIFE_MAX, + Setting.Property.Dynamic, + Setting.Property.NodeScope + ); + + private volatile TimeValue recentReadLoadHalfLifeForNewShards = RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY); + + public SearchStatsSettings(ClusterSettings clusterSettings) { + clusterSettings.initializeAndWatch(RECENT_READ_LOAD_HALF_LIFE_SETTING, value -> recentReadLoadHalfLifeForNewShards = value); + } + + public TimeValue getRecentReadLoadHalfLifeForNewShards() { + return recentReadLoadHalfLifeForNewShards; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java index 6e6f744f6b719..d01ea2a184dbd 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java @@ -10,6 +10,7 @@ package org.elasticsearch.index.search.stats; import org.elasticsearch.common.metrics.CounterMetric; +import org.elasticsearch.common.metrics.ExponentiallyWeightedMovingRate; import org.elasticsearch.common.metrics.MeanMetric; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.util.CollectionUtils; @@ -26,9 +27,15 @@ public final class ShardSearchStats implements SearchOperationListener { - private final StatsHolder totalStats = new StatsHolder(); + private final StatsHolder totalStats; private final CounterMetric openContexts = new CounterMetric(); private volatile Map groupsStats = emptyMap(); + private final SearchStatsSettings searchStatsSettings; + + public ShardSearchStats(SearchStatsSettings searchStatsSettings) { + this.searchStatsSettings = searchStatsSettings; + this.totalStats = new StatsHolder(searchStatsSettings); + } /** * Returns the stats, including group specific stats. If the groups are null/0 length, then nothing @@ -78,9 +85,11 @@ public void onFailedQueryPhase(SearchContext searchContext) { @Override public void onQueryPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, searchContext.hasOnlySuggest() ? statsHolder -> { + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.suggestMetric.inc(tookInNanos); statsHolder.suggestCurrent.dec(); } : statsHolder -> { + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.queryMetric.inc(tookInNanos); statsHolder.queryCurrent.dec(); }); @@ -102,6 +111,7 @@ public void onFailedFetchPhase(SearchContext searchContext) { @Override public void onFetchPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, statsHolder -> { + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.fetchMetric.inc(tookInNanos); statsHolder.fetchCurrent.dec(); }); @@ -123,7 +133,7 @@ private StatsHolder groupStats(String group) { synchronized (this) { stats = groupsStats.get(group); if (stats == null) { - stats = new StatsHolder(); + stats = new StatsHolder(searchStatsSettings); groupsStats = Maps.copyMapWithAddedEntry(groupsStats, group, stats); } } @@ -173,6 +183,13 @@ static final class StatsHolder { final CounterMetric queryFailure = new CounterMetric(); final CounterMetric fetchFailure = new CounterMetric(); + final ExponentiallyWeightedMovingRate recentSearchLoad; + + StatsHolder(SearchStatsSettings searchStatsSettings) { + double lambdaInInverseNanos = Math.log(2.0) / searchStatsSettings.getRecentReadLoadHalfLifeForNewShards().nanos(); + this.recentSearchLoad = new ExponentiallyWeightedMovingRate(lambdaInInverseNanos, System.nanoTime()); + } + SearchStats.Stats stats() { return new SearchStats.Stats( queryMetric.count(), @@ -188,7 +205,8 @@ SearchStats.Stats stats() { scrollCurrent.count(), suggestMetric.count(), TimeUnit.NANOSECONDS.toMillis(suggestMetric.sum()), - suggestCurrent.count() + suggestCurrent.count(), + recentSearchLoad.getRate(System.nanoTime()) ); } } diff --git a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 4b38a5d378ecf..397532a4182f1 100644 --- a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -117,6 +117,7 @@ import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.FieldUsageStats; import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.search.stats.ShardFieldUsageTracker; import org.elasticsearch.index.search.stats.ShardSearchStats; import org.elasticsearch.index.seqno.ReplicationTracker; @@ -203,7 +204,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl private final IndexCache indexCache; private final Store store; private final InternalIndexingStats internalIndexingStats; - private final ShardSearchStats searchStats = new ShardSearchStats(); + private final ShardSearchStats searchStats; private final ShardFieldUsageTracker fieldUsageTracker; private final String shardUuid = UUIDs.randomBase64UUID(); private final long shardCreationTime; @@ -341,7 +342,8 @@ public IndexShard( final LongSupplier relativeTimeInNanosSupplier, final Engine.IndexCommitListener indexCommitListener, final MapperMetrics mapperMetrics, - final IndexingStatsSettings indexingStatsSettings + final IndexingStatsSettings indexingStatsSettings, + final SearchStatsSettings searchStatsSettings ) throws IOException { super(shardRouting.shardId(), indexSettings); assert shardRouting.initializing(); @@ -369,6 +371,7 @@ public IndexShard( this.bulkOperationListener = new ShardBulkStats(); this.globalCheckpointSyncer = globalCheckpointSyncer; this.retentionLeaseSyncer = Objects.requireNonNull(retentionLeaseSyncer); + this.searchStats = new ShardSearchStats(searchStatsSettings); this.searchOperationListener = new SearchOperationListener.CompositeListener( CollectionUtils.appendToCopyNoNullElements(searchOperationListener, searchStats), logger diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesService.java b/server/src/main/java/org/elasticsearch/indices/IndicesService.java index 7825b3513a6b3..ed94017a600ed 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -118,6 +118,7 @@ import org.elasticsearch.index.recovery.RecoveryStats; import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseStats; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SeqNoStats; @@ -281,6 +282,7 @@ public class IndicesService extends AbstractLifecycleComponent private final QueryRewriteInterceptor queryRewriteInterceptor; final SlowLogFieldProvider slowLogFieldProvider; // pkg-private for testingå private final IndexingStatsSettings indexStatsSettings; + private final SearchStatsSettings searchStatsSettings; @Override protected void doStart() { @@ -408,6 +410,7 @@ public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, lon this.searchOperationListeners = builder.searchOperationListener; this.slowLogFieldProvider = builder.slowLogFieldProvider; this.indexStatsSettings = new IndexingStatsSettings(clusterService.getClusterSettings()); + this.searchStatsSettings = new SearchStatsSettings(clusterService.getClusterSettings()); } private static final String DANGLING_INDICES_UPDATE_THREAD_NAME = "DanglingIndices#updateTask"; @@ -797,7 +800,8 @@ private synchronized IndexService createIndexService( slowLogFieldProvider, mapperMetrics, searchOperationListeners, - indexStatsSettings + indexStatsSettings, + searchStatsSettings ); for (IndexingOperationListener operationListener : indexingOperationListeners) { indexModule.addIndexOperationListener(operationListener); @@ -895,7 +899,8 @@ public synchronized MapperService createIndexMapperServiceForValidation(IndexMet slowLogFieldProvider, mapperMetrics, searchOperationListeners, - indexStatsSettings + indexStatsSettings, + searchStatsSettings ); pluginsService.forEach(p -> p.onIndexModule(indexModule)); return indexModule.newIndexMapperService(clusterService, parserConfig, mapperRegistry, scriptService); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java index 70f278bd8d369..e8bbe412cbbab 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java @@ -619,6 +619,7 @@ private static CommonStats createShardLevelCommonStats() { ++iota, ++iota, ++iota, + ++iota, ++iota ); Map groupStats = new HashMap<>(); diff --git a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java index f53d01d4772a1..fcd20894b5a3c 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -67,6 +67,7 @@ import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; @@ -254,7 +255,8 @@ public void testWrapperIsBound() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); module.setReaderWrapper(s -> new Wrapper()); @@ -283,7 +285,8 @@ public void testRegisterIndexStore() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final IndexService indexService = newIndexService(module); @@ -310,7 +313,8 @@ public void testDirectoryWrapper() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); module.setDirectoryWrapper(new TestDirectoryWrapper()); @@ -665,7 +669,8 @@ public void testRegisterCustomRecoveryStateFactory() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final IndexService indexService = newIndexService(module); @@ -689,7 +694,8 @@ public void testIndexCommitListenerIsBound() throws IOException, ExecutionExcept mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final AtomicLong lastAcquiredPrimaryTerm = new AtomicLong(); @@ -793,7 +799,8 @@ private static IndexModule createIndexModule( mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); } diff --git a/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java b/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java index a4430e1c1499d..9bd518ae50475 100644 --- a/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java @@ -22,9 +22,9 @@ public void testShardLevelSearchGroupStats() throws Exception { // let's create two dummy search stats with groups Map groupStats1 = new HashMap<>(); Map groupStats2 = new HashMap<>(); - groupStats2.put("group1", new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)); - SearchStats searchStats1 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 0, groupStats1); - SearchStats searchStats2 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 0, groupStats2); + groupStats2.put("group1", new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0)); + SearchStats searchStats1 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0), 0, groupStats1); + SearchStats searchStats2 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0), 0, groupStats2); // adding these two search stats and checking group stats are correct searchStats1.add(searchStats2); diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java new file mode 100644 index 0000000000000..b1a33893f26c8 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -0,0 +1,216 @@ +/* + * 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.search.stats; + +import org.elasticsearch.action.OriginalIndices; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.mapper.MappingLookup; +import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; +import org.elasticsearch.index.search.stats.ShardSearchStats; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.internal.AliasFilter; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.internal.ShardSearchRequest; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.TestSearchContext; +import org.junit.Before; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class ShardSearchStatsTests extends ESTestCase { + + private static final long TEN_MILLIS = 10; + + private ShardSearchStats shardSearchStatsListener; + + @Before + public void setup() { + ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(); + SearchStatsSettings searchStatsSettings = new SearchStatsSettings(clusterSettings); + this.shardSearchStatsListener = new ShardSearchStats(searchStatsSettings); + } + + public void testQueryPhase() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_SuggestOnly() { + try (SearchContext sc = createSearchContext(true)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_withGroup() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_withGroup_SuggestOnly() { + try (SearchContext sc = createSearchContext(true)) { + + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testQueryPhase_SuggestOnly_Failure() { + try (SearchContext sc = createSearchContext(true)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + } + + public void testQueryPhase_Failure() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + } + + public void testFetchPhase() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testFetchPhase_withGroup() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + } + + public void testFetchPhase_Failure() { + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFailedFetchPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + } + + private static SearchContext createSearchContext(boolean suggested) { + IndexSettings indexSettings = new IndexSettings( + IndexMetadata.builder("index") + .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())) + .numberOfShards(1) + .numberOfReplicas(0) + .creationDate(System.currentTimeMillis()) + .build(), + Settings.EMPTY + ); + + SearchExecutionContext searchExecutionContext = new SearchExecutionContext( + 0, + 0, + indexSettings, + null, + null, + null, + MappingLookup.EMPTY, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + Collections.emptyMap(), + null, + MapperMetrics.NOOP + ); + return new TestSearchContext(searchExecutionContext) { + private final SearchRequest searchquest = new SearchRequest().allowPartialSearchResults(true); + private final ShardSearchRequest request = new ShardSearchRequest( + OriginalIndices.NONE, + suggested ? searchquest.source(new SearchSourceBuilder().suggest(new SuggestBuilder())) : searchquest, + new ShardId("index", "indexUUID", 0), + 0, + 1, + AliasFilter.EMPTY, + 1f, + 0L, + null + ); + + @Override + public ShardSearchRequest request() { + return request; + } + + @Override + public List groupStats() { + return Arrays.asList("group1"); + } + }; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java index 47d9520c5aabb..6ea9f29a5b6b2 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java @@ -59,6 +59,7 @@ import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.ReplicationTracker; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; @@ -560,7 +561,8 @@ protected IndexShard newShard( relativeTimeSupplier, null, MapperMetrics.NOOP, - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); indexShard.addShardFailureCallback(DEFAULT_SHARD_FAILURE_HANDLER); success = true; diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java index fbea440c81e58..b501591ea37d4 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java @@ -409,7 +409,23 @@ private static CommonStats mockCommonStats() { ); commonStats.getIndexing().add(new IndexingStats(indexingStats)); - final SearchStats.Stats searchStats = new SearchStats.Stats(++iota, ++iota, no, no, no, no, no, no, no, no, no, no, no, no); + final SearchStats.Stats searchStats = new SearchStats.Stats( + ++iota, + ++iota, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + Double.valueOf(no) + ); commonStats.getSearch().add(new SearchStats(searchStats, no, null)); final SegmentsStats segmentsStats = new SegmentsStats(); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java index 4ff3895551b96..d01f2e0168a72 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java @@ -186,7 +186,7 @@ private CommonStats mockCommonStats() { final IndexingStats.Stats indexingStats = new IndexingStats.Stats(3L, 4L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, true, 5L, 0, 0, 0, 0.0, 0.0); commonStats.getIndexing().add(new IndexingStats(indexingStats)); - final SearchStats.Stats searchStats = new SearchStats.Stats(6L, 7L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); + final SearchStats.Stats searchStats = new SearchStats.Stats(6L, 7L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0.0); commonStats.getSearch().add(new SearchStats(searchStats, 0L, null)); final BulkStats bulkStats = new BulkStats(0L, 0L, 0L, 0L, 0L); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java index f58059c288d3d..dc64365a503d0 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java @@ -357,7 +357,23 @@ private static NodeStats mockNodeStats() { indicesCommonStats.getQueryCache().add(new QueryCacheStats(++iota, ++iota, ++iota, ++iota, no)); indicesCommonStats.getRequestCache().add(new RequestCacheStats(++iota, ++iota, ++iota, ++iota)); - final SearchStats.Stats searchStats = new SearchStats.Stats(++iota, ++iota, no, no, no, no, no, no, no, no, no, no, no, no); + final SearchStats.Stats searchStats = new SearchStats.Stats( + ++iota, + ++iota, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + Double.valueOf(no) + ); indicesCommonStats.getSearch().add(new SearchStats(searchStats, no, null)); final SegmentsStats segmentsStats = new SegmentsStats(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 4eac3ddf85f1b..fd590630094f2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -45,6 +45,7 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.TestIndexNameExpressionResolver; import org.elasticsearch.license.ClusterStateLicenseService; @@ -461,7 +462,8 @@ public void testOnIndexModuleIsNoOpWithSecurityDisabled() throws Exception { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, List.of(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); security.onIndexModule(indexModule); // indexReaderWrapper is a SetOnce so if Security#onIndexModule had already set an ReaderWrapper we would get an exception here diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java index bd8d15ea809fe..1cfa5669363c4 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.indices.TestIndexNameExpressionResolver; @@ -74,7 +75,8 @@ public void testWatcherDisabledTests() throws Exception { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, List.of(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); // this will trip an assertion if the watcher indexing operation listener is null (which it is) but we try to add it watcher.onIndexModule(indexModule); From 3e6d15a6aa1e378d00322b64cf7bd45196336c0d Mon Sep 17 00:00:00 2001 From: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Date: Wed, 11 Jun 2025 15:42:45 +0200 Subject: [PATCH 068/102] [ESQL] Fix typo in search-functions.md (#129260) ^^ --- .../query-languages/esql/_snippets/lists/search-functions.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/lists/search-functions.md b/docs/reference/query-languages/esql/_snippets/lists/search-functions.md index b735d7e5b8574..b12c6f3f45eb8 100644 --- a/docs/reference/query-languages/esql/_snippets/lists/search-functions.md +++ b/docs/reference/query-languages/esql/_snippets/lists/search-functions.md @@ -2,5 +2,4 @@ * [preview] [`MATCH`](../../functions-operators/search-functions.md#esql-match) * [preview] [`MATCH_PHRASE`](../../functions-operators/search-functions.md#esql-match_phrase) * [preview] [`QSTR`](../../functions-operators/search-functions.md#esql-qstr) - % * [preview] [ - `TERM`](../../functions-operators/search-functions.md#esql-term) +% * [preview] [`TERM`](../../functions-operators/search-functions.md#esql-term) From fad24a466320749c974b4a6724e774622271b962 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Wed, 11 Jun 2025 10:16:38 -0400 Subject: [PATCH 069/102] ESQL: Log partial failures (#129164) Now that ESQL has `allow_partial_results` we can reply with a `200` even though some nodes failed to run ESQL. This could happen because the node is restarting. Or because of a bug. Or a disconnect. All kinds of things. This logs those partial failures so an operator can look at them and get a sense of why they are happening. --- docs/changelog/129164.yaml | 5 + .../esql/action/EsqlResponseListener.java | 24 +++ .../action/EsqlResponseListenerTests.java | 138 ++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 docs/changelog/129164.yaml create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlResponseListenerTests.java diff --git a/docs/changelog/129164.yaml b/docs/changelog/129164.yaml new file mode 100644 index 0000000000000..0d16fdf1b239e --- /dev/null +++ b/docs/changelog/129164.yaml @@ -0,0 +1,5 @@ +pr: 129164 +summary: Log partial failures +area: ES|QL +type: feature +issues: [] diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResponseListener.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResponseListener.java index 3d38b697dc5be..b432339ccf4fd 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResponseListener.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlResponseListener.java @@ -9,6 +9,7 @@ import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.core.Releasable; import org.elasticsearch.core.Releasables; import org.elasticsearch.core.TimeValue; @@ -21,6 +22,7 @@ import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestRefCountedChunkedToXContentListener; +import org.elasticsearch.transport.RemoteClusterAware; import org.elasticsearch.xcontent.MediaType; import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.esql.arrow.ArrowFormat; @@ -30,6 +32,7 @@ import java.io.IOException; import java.util.Locale; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -121,6 +124,7 @@ private EsqlResponseListener(RestChannel channel, RestRequest restRequest, Strin @Override protected void processResponse(EsqlQueryResponse esqlQueryResponse) throws IOException { + logPartialFailures(channel.request().rawPath(), channel.request().params(), esqlQueryResponse.getExecutionInfo()); channel.sendResponse(buildResponse(esqlQueryResponse)); } @@ -229,4 +233,24 @@ private void checkDelimiter() { throw new IllegalArgumentException(message); } } + + /** + * Log all partial request failures to the {@code rest.suppressed} logger + * so an operator can categorize them after the fact. + */ + static void logPartialFailures(String rawPath, Map params, EsqlExecutionInfo executionInfo) { + if (executionInfo == null) { + return; + } + for (EsqlExecutionInfo.Cluster cluster : executionInfo.getClusters().values()) { + for (ShardSearchFailure failure : cluster.getFailures()) { + if (LOGGER.isWarnEnabled()) { + String clusterMessage = cluster.getClusterAlias().equals(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY) + ? "" + : ", cluster: " + cluster.getClusterAlias(); + LOGGER.warn("partial failure at path: {}, params: {}{}", rawPath, params, clusterMessage, failure); + } + } + } + } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlResponseListenerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlResponseListenerTests.java new file mode 100644 index 0000000000000..c96af8b00ab99 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlResponseListenerTests.java @@ -0,0 +1,138 @@ +/* + * 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.action; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.filter.RegexFilter; +import org.elasticsearch.action.search.ShardSearchFailure; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.search.SearchShardTarget; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.RemoteClusterAware; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +public class EsqlResponseListenerTests extends ESTestCase { + private final String LOCAL_CLUSTER_ALIAS = RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY; + + private static MockAppender appender; + static Logger logger = LogManager.getLogger(EsqlResponseListener.class); + + @BeforeClass + public static void init() throws IllegalAccessException { + appender = new MockAppender("testAppender"); + appender.start(); + Configurator.setLevel(logger, Level.DEBUG); + Loggers.addAppender(logger, appender); + } + + @After + public void clear() { + appender.events.clear(); + } + + @AfterClass + public static void cleanup() { + appender.stop(); + Loggers.removeAppender(logger, appender); + } + + public void testLogPartialFailures() { + EsqlExecutionInfo executionInfo = new EsqlExecutionInfo(false); + executionInfo.swapCluster( + LOCAL_CLUSTER_ALIAS, + (k, v) -> new EsqlExecutionInfo.Cluster( + LOCAL_CLUSTER_ALIAS, + "idx", + false, + EsqlExecutionInfo.Cluster.Status.SUCCESSFUL, + 10, + 10, + 3, + 0, + List.of( + new ShardSearchFailure(new Exception("dummy"), target(LOCAL_CLUSTER_ALIAS, 0)), + new ShardSearchFailure(new Exception("error"), target(LOCAL_CLUSTER_ALIAS, 1)) + ), + new TimeValue(4444L) + ) + ); + EsqlResponseListener.logPartialFailures("/_query", Map.of(), executionInfo); + + assertThat(appender.events, hasSize(2)); + LogEvent logEvent = appender.events.get(0); + assertThat(logEvent.getLevel(), equalTo(Level.WARN)); + assertThat(logEvent.getMessage().getFormattedMessage(), equalTo("partial failure at path: /_query, params: {}")); + assertThat(logEvent.getThrown().getCause().getMessage(), equalTo("dummy")); + logEvent = appender.events.get(1); + assertThat(logEvent.getLevel(), equalTo(Level.WARN)); + assertThat(logEvent.getMessage().getFormattedMessage(), equalTo("partial failure at path: /_query, params: {}")); + assertThat(logEvent.getThrown().getCause().getMessage(), equalTo("error")); + } + + public void testLogPartialFailuresRemote() { + EsqlExecutionInfo executionInfo = new EsqlExecutionInfo(false); + executionInfo.swapCluster( + "remote_cluster", + (k, v) -> new EsqlExecutionInfo.Cluster( + "remote_cluster", + "idx", + false, + EsqlExecutionInfo.Cluster.Status.SUCCESSFUL, + 10, + 10, + 3, + 0, + List.of(new ShardSearchFailure(new Exception("dummy"), target("remote_cluster", 0))), + new TimeValue(4444L) + ) + ); + EsqlResponseListener.logPartialFailures("/_query", Map.of(), executionInfo); + + assertThat(appender.events, hasSize(1)); + LogEvent logEvent = appender.events.get(0); + assertThat(logEvent.getLevel(), equalTo(Level.WARN)); + assertThat( + logEvent.getMessage().getFormattedMessage(), + equalTo("partial failure at path: /_query, params: {}, cluster: remote_cluster") + ); + assertThat(logEvent.getThrown().getCause().getMessage(), equalTo("dummy")); + } + + private SearchShardTarget target(String clusterAlias, int shardId) { + return new SearchShardTarget("node", new ShardId("idx", "uuid", shardId), clusterAlias); + } + + private static class MockAppender extends AbstractAppender { + public final List events = new ArrayList<>(); + + MockAppender(final String name) throws IllegalAccessException { + super(name, RegexFilter.createFilter(".*(\n.*)*", new String[0], false, null, null), null, false); + } + + @Override + public void append(LogEvent event) { + events.add(event.toImmutable()); + } + } +} From 5a23779e816240c4e4afb215b2870e71cd73c15a Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Wed, 11 Jun 2025 16:40:05 +0200 Subject: [PATCH 070/102] Update Gradle wrapper to 8.14.2 (#129179) --- build-tools-internal/gradle/wrapper/gradle-wrapper.properties | 4 ++-- build-tools-internal/src/main/resources/minimumGradleVersion | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- plugins/examples/gradle/wrapper/gradle-wrapper.properties | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build-tools-internal/gradle/wrapper/gradle-wrapper.properties b/build-tools-internal/gradle/wrapper/gradle-wrapper.properties index 3d25d539e993f..c4a852da571d7 100644 --- a/build-tools-internal/gradle/wrapper/gradle-wrapper.properties +++ b/build-tools-internal/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=d7042b3c11565c192041fc8c4703f541b888286404b4f267138c1d094d8ecdca -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip +distributionSha256Sum=443c9c8ee2ac1ee0e11881a40f2376d79c66386264a44b24a9f8ca67e633375f +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/build-tools-internal/src/main/resources/minimumGradleVersion b/build-tools-internal/src/main/resources/minimumGradleVersion index 3e2c3fbefcb12..4dd804f10624c 100644 --- a/build-tools-internal/src/main/resources/minimumGradleVersion +++ b/build-tools-internal/src/main/resources/minimumGradleVersion @@ -1 +1 @@ -8.14.1 \ No newline at end of file +8.14.2 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3d25d539e993f..c4a852da571d7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=d7042b3c11565c192041fc8c4703f541b888286404b4f267138c1d094d8ecdca -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip +distributionSha256Sum=443c9c8ee2ac1ee0e11881a40f2376d79c66386264a44b24a9f8ca67e633375f +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/plugins/examples/gradle/wrapper/gradle-wrapper.properties b/plugins/examples/gradle/wrapper/gradle-wrapper.properties index 3d25d539e993f..c4a852da571d7 100644 --- a/plugins/examples/gradle/wrapper/gradle-wrapper.properties +++ b/plugins/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=d7042b3c11565c192041fc8c4703f541b888286404b4f267138c1d094d8ecdca -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip +distributionSha256Sum=443c9c8ee2ac1ee0e11881a40f2376d79c66386264a44b24a9f8ca67e633375f +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 5acb2fa16f5a2c1fc40a44ce0662fab7d7e8b643 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Wed, 11 Jun 2025 11:02:37 -0400 Subject: [PATCH 071/102] Fix ivf nodestats impl for getOffHeapByteSize (#129259) This fixes a silly bug where we didn't override `OffHeapStats` for IVF. --- .../vectors/DefaultIVFVectorsReader.java | 59 ++----------------- .../codec/vectors/IVFVectorsFormatTests.java | 34 +++++++++++ 2 files changed, 40 insertions(+), 53 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java index 36a7c2084a4a5..ab8ad21674177 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsReader.java @@ -11,7 +11,6 @@ import org.apache.lucene.codecs.hnsw.FlatVectorsReader; import org.apache.lucene.index.FieldInfo; -import org.apache.lucene.index.FloatVectorValues; import org.apache.lucene.index.SegmentReadState; import org.apache.lucene.index.VectorSimilarityFunction; import org.apache.lucene.search.KnnCollector; @@ -20,10 +19,12 @@ import org.apache.lucene.util.VectorUtil; import org.apache.lucene.util.hnsw.NeighborQueue; import org.apache.lucene.util.quantization.OptimizedScalarQuantizer; +import org.elasticsearch.index.codec.vectors.reflect.OffHeapStats; import org.elasticsearch.simdvec.ES91OSQVectorsScorer; import org.elasticsearch.simdvec.ESVectorUtil; import java.io.IOException; +import java.util.Map; import java.util.function.IntPredicate; import static org.apache.lucene.codecs.lucene102.Lucene102BinaryQuantizedVectorsFormat.QUERY_BITS; @@ -38,7 +39,7 @@ * Default implementation of {@link IVFVectorsReader}. It scores the posting lists centroids using * brute force and then scores the top ones using the posting list. */ -public class DefaultIVFVectorsReader extends IVFVectorsReader { +public class DefaultIVFVectorsReader extends IVFVectorsReader implements OffHeapStats { private static final float FOUR_BIT_SCALE = 1f / ((1 << 4) - 1); public DefaultIVFVectorsReader(SegmentReadState state, FlatVectorsReader rawVectorsReader) throws IOException { @@ -163,57 +164,9 @@ static float int4QuantizedScore( } } - static class OffHeapCentroidFloatVectorValues extends FloatVectorValues { - private final int numCentroids; - private final IndexInput input; - private final int dimension; - private final float[] centroid; - private final long centroidByteSize; - private int ord = -1; - - OffHeapCentroidFloatVectorValues(int numCentroids, IndexInput input, int dimension) { - this.numCentroids = numCentroids; - this.input = input; - this.dimension = dimension; - this.centroid = new float[dimension]; - this.centroidByteSize = dimension + 3 * Float.BYTES + Short.BYTES; - } - - @Override - public float[] vectorValue(int ord) throws IOException { - if (ord < 0 || ord >= numCentroids) { - throw new IllegalArgumentException("ord must be in [0, " + numCentroids + "]"); - } - if (ord == this.ord) { - return centroid; - } - readQuantizedCentroid(ord); - return centroid; - } - - private void readQuantizedCentroid(int centroidOrdinal) throws IOException { - if (centroidOrdinal == ord) { - return; - } - input.seek(numCentroids * centroidByteSize + (long) Float.BYTES * dimension * centroidOrdinal); - input.readFloats(centroid, 0, centroid.length); - ord = centroidOrdinal; - } - - @Override - public int dimension() { - return dimension; - } - - @Override - public int size() { - return numCentroids; - } - - @Override - public FloatVectorValues copy() throws IOException { - return new OffHeapCentroidFloatVectorValues(numCentroids, input.clone(), dimension); - } + @Override + public Map getOffHeapByteSize(FieldInfo fieldInfo) { + return Map.of(); } private static class MemorySegmentPostingsVisitor implements PostingVisitor { diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormatTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormatTests.java index f7eb4cf5241ce..177a3d00c3dc4 100644 --- a/server/src/test/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormatTests.java +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormatTests.java @@ -13,13 +13,25 @@ import org.apache.lucene.codecs.Codec; import org.apache.lucene.codecs.FilterCodec; import org.apache.lucene.codecs.KnnVectorsFormat; +import org.apache.lucene.codecs.KnnVectorsReader; +import org.apache.lucene.codecs.perfield.PerFieldKnnVectorsFormat; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.KnnFloatVectorField; +import org.apache.lucene.index.CodecReader; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.VectorEncoding; import org.apache.lucene.index.VectorSimilarityFunction; +import org.apache.lucene.store.Directory; import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; import org.apache.lucene.tests.util.TestUtil; import org.elasticsearch.common.logging.LogConfigurator; +import org.elasticsearch.index.codec.vectors.reflect.OffHeapByteSizeUtils; import org.junit.Before; +import java.io.IOException; import java.util.List; import java.util.Locale; @@ -94,4 +106,26 @@ public void testLimits() { expectThrows(IllegalArgumentException.class, () -> new IVFVectorsFormat(MIN_VECTORS_PER_CLUSTER - 1)); expectThrows(IllegalArgumentException.class, () -> new IVFVectorsFormat(MAX_VECTORS_PER_CLUSTER + 1)); } + + public void testSimpleOffHeapSize() throws IOException { + float[] vector = randomVector(random().nextInt(12, 500)); + try (Directory dir = newDirectory(); IndexWriter w = new IndexWriter(dir, newIndexWriterConfig())) { + Document doc = new Document(); + doc.add(new KnnFloatVectorField("f", vector, VectorSimilarityFunction.EUCLIDEAN)); + w.addDocument(doc); + w.commit(); + try (IndexReader reader = DirectoryReader.open(w)) { + LeafReader r = getOnlyLeafReader(reader); + if (r instanceof CodecReader codecReader) { + KnnVectorsReader knnVectorsReader = codecReader.getVectorReader(); + if (knnVectorsReader instanceof PerFieldKnnVectorsFormat.FieldsReader fieldsReader) { + knnVectorsReader = fieldsReader.getFieldReader("f"); + } + var fieldInfo = r.getFieldInfos().fieldInfo("f"); + var offHeap = OffHeapByteSizeUtils.getOffHeapByteSize(knnVectorsReader, fieldInfo); + assertEquals(0, offHeap.size()); + } + } + } + } } From 29192c8014ac9e3690d615a8fd93ee7867ad0edc Mon Sep 17 00:00:00 2001 From: kruskall <99559985+kruskall@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:13:45 +0200 Subject: [PATCH 072/102] feat: enable date_detection for all apm data streams (#128913) * feat: enable date_detection for all apm data streams * Update resources.yaml * Create 128913.yml --------- Co-authored-by: Carson Ip --- docs/changelog/128913.yml | 5 +++++ .../src/main/resources/component-templates/apm@mappings.yaml | 1 - x-pack/plugin/apm-data/src/main/resources/resources.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 docs/changelog/128913.yml diff --git a/docs/changelog/128913.yml b/docs/changelog/128913.yml new file mode 100644 index 0000000000000..623897bb272a1 --- /dev/null +++ b/docs/changelog/128913.yml @@ -0,0 +1,5 @@ +pr: 128913 +summary: "[apm-data] Enable 'date_detection' for all apm data streams" +area: Data streams +type: enhancement +issues: [] diff --git a/x-pack/plugin/apm-data/src/main/resources/component-templates/apm@mappings.yaml b/x-pack/plugin/apm-data/src/main/resources/component-templates/apm@mappings.yaml index a5a3a7433f4c1..ac6462c86676c 100644 --- a/x-pack/plugin/apm-data/src/main/resources/component-templates/apm@mappings.yaml +++ b/x-pack/plugin/apm-data/src/main/resources/component-templates/apm@mappings.yaml @@ -4,7 +4,6 @@ _meta: managed: true template: mappings: - date_detection: false dynamic: true dynamic_templates: - numeric_labels: diff --git a/x-pack/plugin/apm-data/src/main/resources/resources.yaml b/x-pack/plugin/apm-data/src/main/resources/resources.yaml index beca2e5890bc0..ea54fd4c072e9 100644 --- a/x-pack/plugin/apm-data/src/main/resources/resources.yaml +++ b/x-pack/plugin/apm-data/src/main/resources/resources.yaml @@ -1,7 +1,7 @@ # "version" holds the version of the templates and ingest pipelines installed # by xpack-plugin apm-data. This must be increased whenever an existing template or # pipeline is changed, in order for it to be updated on Elasticsearch upgrade. -version: 13 +version: 14 component-templates: # Data lifecycle. From bc7d5b2f5ee0a3b08d7b5265ed892801907c1c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenzo=20Dematt=C3=A9?= Date: Wed, 11 Jun 2025 17:22:54 +0200 Subject: [PATCH 073/102] [BC Upgrage] Fix incorrect version parsing in tests (#129243) This PR introduces several fixes to various IT tests, related to the use and misuse of the version identifier for the start cluster: wherever we can, we replace of versions in test code with features where we can't, we make sure we use the actual stack version (the one provided by -Dtests.bwc.main.version and not the bogus "0.0.0" version string) when requesting the cluster version we make sure we do use the "unresolved" version identifier (the value of the tests.old_cluster_version system property e.g. 0.0.0 ) so we resolve the right distribution These changes enabled the tests to be used in BC upgrade tests (and potentially in serverless upgrade tests too, where they would have also failed) Relates to ES-12010 Precedes #128614, #128823 and #128983 --- ...rameterizedFullClusterRestartTestCase.java | 6 ++- .../AbstractRollingUpgradeTestCase.java | 10 +++-- ...actRollingUpgradeWithSecurityTestCase.java | 9 ++-- .../upgrades/DenseVectorMappingUpdateIT.java | 2 +- .../upgrades/FileSettingsUpgradeIT.java | 11 ++++- .../upgrades/LogsUsageRollingUpgradeIT.java | 2 +- .../ParameterizedRollingUpgradeTestCase.java | 21 +++++---- .../upgrades/SnapshotBasedRecoveryIT.java | 20 ++++++--- .../upgrades/SourceModeRollingUpgradeIT.java | 4 +- .../upgrades/VectorSearchIT.java | 43 +++++++------------ .../test/cluster/util/Version.java | 18 ++++++++ .../AzureOpenAiServiceUpgradeIT.java | 9 ++-- .../application/CohereServiceUpgradeIT.java | 19 ++++---- .../HuggingFaceServiceUpgradeIT.java | 18 ++++---- .../application/InferenceUpgradeTestCase.java | 6 ++- .../application/OpenAiServiceUpgradeIT.java | 27 ++++++------ 16 files changed, 128 insertions(+), 97 deletions(-) diff --git a/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedFullClusterRestartTestCase.java b/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedFullClusterRestartTestCase.java index 7518a799540b8..5c83fee97cdca 100644 --- a/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedFullClusterRestartTestCase.java +++ b/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedFullClusterRestartTestCase.java @@ -134,7 +134,11 @@ public boolean isRunningAgainstOldCluster() { return requestedUpgradeStatus == OLD; } - public static String getOldClusterVersion() { + /** + * The version of the "old" (initial) cluster. It is an opaque string, do not even think about parsing it for version + * comparison. Use (test) cluster features and {@link ParameterizedFullClusterRestartTestCase#oldClusterHasFeature} instead. + */ + protected static String getOldClusterVersion() { return System.getProperty("tests.bwc.main.version", OLD_CLUSTER_VERSION); } diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeTestCase.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeTestCase.java index 99db4c2677559..87096277cfce6 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeTestCase.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeTestCase.java @@ -30,10 +30,11 @@ public abstract class AbstractRollingUpgradeTestCase extends ParameterizedRollin private static final ElasticsearchCluster cluster = buildCluster(); private static ElasticsearchCluster buildCluster() { - Version oldVersion = Version.fromString(OLD_CLUSTER_VERSION); + // Note we need to use OLD_CLUSTER_VERSION directly here, as it may contain special values (e.g. 0.0.0) the ElasticsearchCluster + // builder uses to lookup a particular distribution var cluster = ElasticsearchCluster.local() .distribution(DistributionType.DEFAULT) - .version(getOldClusterTestVersion()) + .version(OLD_CLUSTER_VERSION) .nodes(NODE_NUM) .setting("path.repo", new Supplier<>() { @Override @@ -46,8 +47,9 @@ public String get() { .feature(FeatureFlag.TIME_SERIES_MODE); // Avoid triggering bogus assertion when serialized parsed mappings don't match with original mappings, because _source key is - // inconsistent - if (oldVersion.before(Version.fromString("8.18.0"))) { + // inconsistent. As usual, we operate under the premise that "versionless" clusters (serverless) are on the latest code and + // do not need this. + if (Version.tryParse(getOldClusterVersion()).map(v -> v.before(Version.fromString("8.18.0"))).orElse(false)) { cluster.jvmArg("-da:org.elasticsearch.index.mapper.DocumentMapper"); cluster.jvmArg("-da:org.elasticsearch.index.mapper.MapperService"); } diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeWithSecurityTestCase.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeWithSecurityTestCase.java index 9f74573ca83fb..961e4a353df95 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeWithSecurityTestCase.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/AbstractRollingUpgradeWithSecurityTestCase.java @@ -35,10 +35,11 @@ public abstract class AbstractRollingUpgradeWithSecurityTestCase extends Paramet private static final ElasticsearchCluster cluster = buildCluster(); private static ElasticsearchCluster buildCluster() { - Version oldVersion = Version.fromString(OLD_CLUSTER_VERSION); + // Note we need to use OLD_CLUSTER_VERSION directly here, as it may contain special values (e.g. 0.0.0) the ElasticsearchCluster + // builder uses to lookup a particular distribution var cluster = ElasticsearchCluster.local() .distribution(DistributionType.DEFAULT) - .version(getOldClusterTestVersion()) + .version(OLD_CLUSTER_VERSION) .nodes(NODE_NUM) .user(USER, PASS) .setting("xpack.security.autoconfiguration.enabled", "false") @@ -51,8 +52,8 @@ public String get() { }); // Avoid triggering bogus assertion when serialized parsed mappings don't match with original mappings, because _source key is - // inconsistent - if (oldVersion.before(Version.fromString("8.18.0"))) { + // inconsistent. Assume non-parseable versions (serverless) do not need this. + if (Version.tryParse(getOldClusterVersion()).map(v -> v.before(Version.fromString("8.18.0"))).orElse(false)) { cluster.jvmArg("-da:org.elasticsearch.index.mapper.DocumentMapper"); cluster.jvmArg("-da:org.elasticsearch.index.mapper.MapperService"); } diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DenseVectorMappingUpdateIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DenseVectorMappingUpdateIT.java index b8fc3b15636fb..dff0e94193054 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DenseVectorMappingUpdateIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DenseVectorMappingUpdateIT.java @@ -82,7 +82,7 @@ public DenseVectorMappingUpdateIT(@Name("upgradedNodes") int upgradedNodes) { } public void testDenseVectorMappingUpdateOnOldCluster() throws IOException { - if (getOldClusterTestVersion().after(Version.V_8_7_0.toString())) { + if (oldClusterHasFeature("gte_v8.7.1")) { String indexName = "test_index"; if (isOldCluster()) { Request createIndex = new Request("PUT", "/" + indexName); diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java index 0f48fb65b41f3..6d16714cc67bb 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/FileSettingsUpgradeIT.java @@ -14,6 +14,7 @@ import org.elasticsearch.client.Request; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.core.UpdateForV10; import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.test.cluster.FeatureFlag; import org.elasticsearch.test.cluster.local.distribution.DistributionType; @@ -33,8 +34,12 @@ public class FileSettingsUpgradeIT extends ParameterizedRollingUpgradeTestCase { + @UpdateForV10(owner = UpdateForV10.Owner.CORE_INFRA) // Remove this rule entirely private static final RunnableTestRuleAdapter versionLimit = new RunnableTestRuleAdapter( - () -> assumeTrue("Only valid when upgrading from pre-file settings", getOldClusterTestVersion().before(new Version(8, 4, 0))) + () -> assumeTrue( + "Only valid when upgrading from pre-file settings", + Version.tryParse(getOldClusterVersion()).map(v -> v.before(new Version(8, 4, 0))).orElse(false) + ) ); private static final String settingsJSON = """ @@ -52,9 +57,11 @@ public class FileSettingsUpgradeIT extends ParameterizedRollingUpgradeTestCase { private static final TemporaryFolder repoDirectory = new TemporaryFolder(); + // Note we need to use OLD_CLUSTER_VERSION directly here, as it may contain special values (e.g. 0.0.0) the ElasticsearchCluster + // builder uses to lookup a particular distribution private static final ElasticsearchCluster cluster = ElasticsearchCluster.local() .distribution(DistributionType.DEFAULT) - .version(getOldClusterTestVersion()) + .version(OLD_CLUSTER_VERSION) .nodes(NODE_NUM) .setting("path.repo", new Supplier<>() { @Override diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/LogsUsageRollingUpgradeIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/LogsUsageRollingUpgradeIT.java index b72060a34489e..6fea6917bebc0 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/LogsUsageRollingUpgradeIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/LogsUsageRollingUpgradeIT.java @@ -30,7 +30,7 @@ public LogsUsageRollingUpgradeIT(@Name("upgradedNodes") int upgradedNodes) { } public void testUsage() throws Exception { - assumeTrue("logsdb.prior_logs_usage only gets set in 8.x", getOldClusterTestVersion().before("9.0.0")); + assumeFalse("logsdb.prior_logs_usage only gets set in 8.x", oldClusterHasFeature("gte_v9.0.0")); String dataStreamName = "logs-mysql-error"; if (isOldCluster()) { bulkIndex(dataStreamName, 4, 256, Instant.now()); diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedRollingUpgradeTestCase.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedRollingUpgradeTestCase.java index 00adf18bba6e9..43a9081964483 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedRollingUpgradeTestCase.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/ParameterizedRollingUpgradeTestCase.java @@ -127,11 +127,6 @@ public static void resetNodes() { upgradeFailed = false; } - @Deprecated // Use the new testing framework and oldClusterHasFeature(feature) instead - protected static String getOldClusterVersion() { - return OLD_CLUSTER_VERSION; - } - protected static boolean oldClusterHasFeature(String featureId) { assert oldClusterTestFeatureService != null; return oldClusterTestFeatureService.clusterHasFeature(featureId); @@ -146,12 +141,20 @@ protected static IndexVersion getOldClusterIndexVersion() { return oldIndexVersion; } - protected static Version getOldClusterTestVersion() { - return Version.fromString(OLD_CLUSTER_VERSION); + /** + * The version of the "old" (initial) cluster. It is an opaque string, do not even think about parsing it for version + * comparison. Use (test) cluster features and {@link ParameterizedRollingUpgradeTestCase#oldClusterHasFeature} instead. + */ + protected static String getOldClusterVersion() { + return System.getProperty("tests.bwc.main.version", OLD_CLUSTER_VERSION); } - protected static boolean isOldClusterVersion(String nodeVersion) { - return OLD_CLUSTER_VERSION.equals(nodeVersion); + protected static boolean isOldClusterVersion(String nodeVersion, String buildHash) { + String bwcRefSpec = System.getProperty("tests.bwc.refspec.main"); + if (bwcRefSpec != null) { + return bwcRefSpec.equals(buildHash); + } + return getOldClusterVersion().equals(nodeVersion); } protected static boolean isOldCluster() { diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SnapshotBasedRecoveryIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SnapshotBasedRecoveryIT.java index 485e1d4f28826..312c238cb17e1 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SnapshotBasedRecoveryIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SnapshotBasedRecoveryIT.java @@ -88,7 +88,7 @@ public void testSnapshotBasedRecovery() throws Exception { } String primaryNodeId = getPrimaryNodeIdOfShard(indexName, 0); - String primaryNodeVersion = getNodeVersion(primaryNodeId); + var primaryNodeVersion = getNodeVersion(primaryNodeId); // Sometimes the primary shard ends on the upgraded node (i.e. after a rebalance) // This causes issues when removing and adding replicas, since then we cannot allocate to any of the old nodes. @@ -96,13 +96,14 @@ public void testSnapshotBasedRecovery() throws Exception { // In that case we exclude the upgraded node from the shard allocation and cancel the shard to force moving // the primary to a node in the old version, this allows adding replicas in the first mixed round. logger.info("--> Primary node in first mixed round {} / {}", primaryNodeId, primaryNodeVersion); - if (isOldClusterVersion(primaryNodeVersion) == false) { + if (isOldClusterVersion(primaryNodeVersion.version(), primaryNodeVersion.buildHash()) == false) { logger.info("--> cancelling primary shard on node [{}]", primaryNodeId); cancelShard(indexName, 0, primaryNodeId); logger.info("--> done cancelling primary shard on node [{}]", primaryNodeId); String currentPrimaryNodeId = getPrimaryNodeIdOfShard(indexName, 0); - assertTrue(isOldClusterVersion(getNodeVersion(currentPrimaryNodeId))); + var currentPrimaryNodeVersion = getNodeVersion(currentPrimaryNodeId); + assertTrue(isOldClusterVersion(currentPrimaryNodeVersion.version(), currentPrimaryNodeVersion.buildHash())); } } else { logger.info("--> not in first upgrade round, removing exclusions for [{}]", indexName); @@ -137,17 +138,24 @@ private List getUpgradedNodeIds() throws IOException { List upgradedNodes = new ArrayList<>(); for (Map.Entry> nodeInfoEntry : nodes.entrySet()) { String nodeVersion = extractValue(nodeInfoEntry.getValue(), "version"); - if (isOldClusterVersion(nodeVersion) == false) { + String nodeBuildHash = extractValue(nodeInfoEntry.getValue(), "build_hash"); + if (isOldClusterVersion(nodeVersion, nodeBuildHash) == false) { upgradedNodes.add(nodeInfoEntry.getKey()); } } return upgradedNodes; } - private String getNodeVersion(String primaryNodeId) throws IOException { + private record NodeVersion(String version, String buildHash) {} + + private NodeVersion getNodeVersion(String primaryNodeId) throws IOException { Request request = new Request(HttpGet.METHOD_NAME, "_nodes/" + primaryNodeId); Response response = client().performRequest(request); - return extractValue(responseAsMap(response), "nodes." + primaryNodeId + ".version"); + Map responseAsMap = responseAsMap(response); + return new NodeVersion( + extractValue(responseAsMap, "nodes." + primaryNodeId + ".version"), + extractValue(responseAsMap, "nodes." + primaryNodeId + ".build_hash") + ); } private String getPrimaryNodeIdOfShard(String indexName, int shard) throws Exception { diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SourceModeRollingUpgradeIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SourceModeRollingUpgradeIT.java index 55a03da7c9e90..b512835b8201d 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SourceModeRollingUpgradeIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/SourceModeRollingUpgradeIT.java @@ -27,7 +27,7 @@ public SourceModeRollingUpgradeIT(@Name("upgradedNodes") int upgradedNodes) { } public void testConfigureStoredSourceBeforeIndexCreationLegacy() throws IOException { - assumeTrue("testing deprecation warnings and deprecation migrations", getOldClusterTestVersion().before("9.0.0")); + assumeFalse("testing deprecation warnings and deprecation migrations", oldClusterHasFeature("gte_v9.0.0")); String templateName = "logs@custom"; if (isOldCluster()) { var storedSourceMapping = """ @@ -56,7 +56,7 @@ public void testConfigureStoredSourceBeforeIndexCreationLegacy() throws IOExcept } public void testConfigureStoredSourceWhenIndexIsCreatedLegacy() throws IOException { - assumeTrue("testing deprecation warnings and deprecation migrations", getOldClusterTestVersion().before("9.0.0")); + assumeFalse("testing deprecation warnings and deprecation migrations", oldClusterHasFeature("gte_v9.0.0")); String templateName = "logs@custom"; if (isOldCluster()) { var storedSourceMapping = """ diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java index 07034618be4a6..afee17cd82e2d 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java @@ -37,14 +37,16 @@ public VectorSearchIT(@Name("upgradedNodes") int upgradedNodes) { private static final String BBQ_INDEX_NAME = "bbq_vector_index"; private static final String FLAT_QUANTIZED_INDEX_NAME = "flat_quantized_vector_index"; private static final String FLAT_BBQ_INDEX_NAME = "flat_bbq_vector_index"; - private static final String FLOAT_VECTOR_SEARCH_VERSION = "8.4.0"; - private static final String BYTE_VECTOR_SEARCH_VERSION = "8.6.0"; - private static final String QUANTIZED_VECTOR_SEARCH_VERSION = "8.12.1"; - private static final String FLAT_QUANTIZED_VECTOR_SEARCH_VERSION = "8.13.0"; - private static final String BBQ_VECTOR_SEARCH_VERSION = "8.18.0"; + + // TODO: replace these with proper test features + private static final String FLOAT_VECTOR_SEARCH_TEST_FEATURE = "gte_v8.4.0"; + private static final String BYTE_VECTOR_SEARCH_TEST_FEATURE = "gte_v8.6.0"; + private static final String QUANTIZED_VECTOR_SEARCH_TEST_FEATURE = "gte_v8.12.1"; + private static final String FLAT_QUANTIZED_VECTOR_SEARCH_TEST_FEATURE = "gte_v8.13.0"; + private static final String BBQ_VECTOR_SEARCH_TEST_FEATURE = "gte_v8.18.0"; public void testScriptByteVectorSearch() throws Exception { - assumeTrue("byte vector search is not supported on this version", getOldClusterTestVersion().onOrAfter(BYTE_VECTOR_SEARCH_VERSION)); + assumeTrue("byte vector search is not supported on this version", oldClusterHasFeature(BYTE_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { // create index and index 10 random floating point vectors String mapping = """ @@ -91,10 +93,7 @@ public void testScriptByteVectorSearch() throws Exception { } public void testScriptVectorSearch() throws Exception { - assumeTrue( - "Float vector search is not supported on this version", - getOldClusterTestVersion().onOrAfter(FLOAT_VECTOR_SEARCH_VERSION) - ); + assumeTrue("Float vector search is not supported on this version", oldClusterHasFeature(FLOAT_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { // create index and index 10 random floating point vectors String mapping = """ @@ -140,10 +139,7 @@ public void testScriptVectorSearch() throws Exception { } public void testFloatVectorSearch() throws Exception { - assumeTrue( - "Float vector search is not supported on this version", - getOldClusterTestVersion().onOrAfter(FLOAT_VECTOR_SEARCH_VERSION) - ); + assumeTrue("Float vector search is not supported on this version", oldClusterHasFeature(FLOAT_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { String mapping = """ { @@ -215,7 +211,7 @@ public void testFloatVectorSearch() throws Exception { } public void testByteVectorSearch() throws Exception { - assumeTrue("Byte vector search is not supported on this version", getOldClusterTestVersion().onOrAfter(BYTE_VECTOR_SEARCH_VERSION)); + assumeTrue("Byte vector search is not supported on this version", oldClusterHasFeature(BYTE_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { String mapping = """ { @@ -288,10 +284,7 @@ public void testByteVectorSearch() throws Exception { } public void testQuantizedVectorSearch() throws Exception { - assumeTrue( - "Quantized vector search is not supported on this version", - getOldClusterTestVersion().onOrAfter(QUANTIZED_VECTOR_SEARCH_VERSION) - ); + assumeTrue("Quantized vector search is not supported on this version", oldClusterHasFeature(QUANTIZED_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { String mapping = """ { @@ -364,7 +357,7 @@ public void testQuantizedVectorSearch() throws Exception { public void testFlatQuantizedVectorSearch() throws Exception { assumeTrue( "Quantized vector search is not supported on this version", - getOldClusterTestVersion().onOrAfter(FLAT_QUANTIZED_VECTOR_SEARCH_VERSION) + oldClusterHasFeature(FLAT_QUANTIZED_VECTOR_SEARCH_TEST_FEATURE) ); if (isOldCluster()) { String mapping = """ @@ -434,10 +427,7 @@ public void testFlatQuantizedVectorSearch() throws Exception { } public void testBBQVectorSearch() throws Exception { - assumeTrue( - "Quantized vector search is not supported on this version", - getOldClusterTestVersion().onOrAfter(BBQ_VECTOR_SEARCH_VERSION) - ); + assumeTrue("Quantized vector search is not supported on this version", oldClusterHasFeature(BBQ_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { String mapping = """ { @@ -518,10 +508,7 @@ public void testBBQVectorSearch() throws Exception { } public void testFlatBBQVectorSearch() throws Exception { - assumeTrue( - "Quantized vector search is not supported on this version", - getOldClusterTestVersion().onOrAfter(BBQ_VECTOR_SEARCH_VERSION) - ); + assumeTrue("Quantized vector search is not supported on this version", oldClusterHasFeature(BBQ_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { String mapping = """ { diff --git a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/Version.java b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/Version.java index 7a07876e2fb32..2136d88a9e2aa 100644 --- a/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/Version.java +++ b/test/test-clusters/src/main/java/org/elasticsearch/test/cluster/util/Version.java @@ -13,6 +13,7 @@ import java.io.Serializable; import java.io.UncheckedIOException; import java.util.Objects; +import java.util.Optional; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -96,6 +97,23 @@ public static Version fromString(final String s, final Mode mode) { return new Version(Integer.parseInt(major), Integer.parseInt(minor), revision == null ? 0 : Integer.parseInt(revision), qualifier); } + public static Optional tryParse(final String s) { + Objects.requireNonNull(s); + Matcher matcher = pattern.matcher(s); + if (matcher.matches() == false) { + return Optional.empty(); + } + + String major = matcher.group(1); + String minor = matcher.group(2); + String revision = matcher.group(3); + String qualifier = matcher.group(4); + + return Optional.of( + new Version(Integer.parseInt(major), Integer.parseInt(minor), revision == null ? 0 : Integer.parseInt(revision), qualifier) + ); + } + @Override public String toString() { return getMajor() + "." + getMinor() + "." + getRevision(); diff --git a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/AzureOpenAiServiceUpgradeIT.java b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/AzureOpenAiServiceUpgradeIT.java index f0196834b9175..82ebc755bdca5 100644 --- a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/AzureOpenAiServiceUpgradeIT.java +++ b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/AzureOpenAiServiceUpgradeIT.java @@ -26,7 +26,7 @@ public class AzureOpenAiServiceUpgradeIT extends InferenceUpgradeTestCase { - private static final String OPEN_AI_AZURE_EMBEDDINGS_ADDED = "8.14.0"; + private static final String OPEN_AI_AZURE_EMBEDDINGS_ADDED_FEATURE = "gte_v8.14.0"; private static MockWebServer openAiEmbeddingsServer; @@ -48,10 +48,9 @@ public static void shutdown() { @SuppressWarnings("unchecked") @AwaitsFix(bugUrl = "Cannot set the URL in the tests") public void testOpenAiEmbeddings() throws IOException { - var openAiEmbeddingsSupported = getOldClusterTestVersion().onOrAfter(OPEN_AI_AZURE_EMBEDDINGS_ADDED); - // `gte_v` indicates that the cluster version is Greater Than or Equal to MODELS_RENAMED_TO_ENDPOINTS - String oldClusterEndpointIdentifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("Azure OpenAI embedding service added in " + OPEN_AI_AZURE_EMBEDDINGS_ADDED, openAiEmbeddingsSupported); + var openAiEmbeddingsSupported = oldClusterHasFeature(OPEN_AI_AZURE_EMBEDDINGS_ADDED_FEATURE); + String oldClusterEndpointIdentifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("Azure OpenAI embedding service supported", openAiEmbeddingsSupported); final String oldClusterId = "old-cluster-embeddings"; final String upgradedClusterId = "upgraded-cluster-embeddings"; diff --git a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/CohereServiceUpgradeIT.java b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/CohereServiceUpgradeIT.java index 0acbc148515bd..7851a093a95cb 100644 --- a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/CohereServiceUpgradeIT.java +++ b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/CohereServiceUpgradeIT.java @@ -33,9 +33,9 @@ public class CohereServiceUpgradeIT extends InferenceUpgradeTestCase { - private static final String COHERE_EMBEDDINGS_ADDED = "8.13.0"; - private static final String COHERE_RERANK_ADDED = "8.14.0"; - private static final String BYTE_ALIAS_FOR_INT8_ADDED = "8.14.0"; + // TODO: replace with proper test features + private static final String COHERE_EMBEDDINGS_ADDED_TEST_FEATURE = "gte_v8.13.0"; + private static final String COHERE_RERANK_ADDED_TEST_FEATURE = "gte_v8.14.0"; private static MockWebServer cohereEmbeddingsServer; private static MockWebServer cohereRerankServer; @@ -61,10 +61,9 @@ public static void shutdown() { @SuppressWarnings("unchecked") public void testCohereEmbeddings() throws IOException { - var embeddingsSupported = getOldClusterTestVersion().onOrAfter(COHERE_EMBEDDINGS_ADDED); - // `gte_v` indicates that the cluster version is Greater Than or Equal to MODELS_RENAMED_TO_ENDPOINTS - String oldClusterEndpointIdentifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("Cohere embedding service added in " + COHERE_EMBEDDINGS_ADDED, embeddingsSupported); + var embeddingsSupported = oldClusterHasFeature(COHERE_EMBEDDINGS_ADDED_TEST_FEATURE); + String oldClusterEndpointIdentifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("Cohere embedding service supported", embeddingsSupported); final String oldClusterIdInt8 = "old-cluster-embeddings-int8"; final String oldClusterIdFloat = "old-cluster-embeddings-float"; @@ -191,9 +190,9 @@ void assertEmbeddingInference(String inferenceId, CohereEmbeddingType type) thro @SuppressWarnings("unchecked") public void testRerank() throws IOException { - var rerankSupported = getOldClusterTestVersion().onOrAfter(COHERE_RERANK_ADDED); - String old_cluster_endpoint_identifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("Cohere rerank service added in " + COHERE_RERANK_ADDED, rerankSupported); + var rerankSupported = oldClusterHasFeature(COHERE_RERANK_ADDED_TEST_FEATURE); + String old_cluster_endpoint_identifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("Cohere rerank service supported", rerankSupported); final String oldClusterId = "old-cluster-rerank"; final String upgradedClusterId = "upgraded-cluster-rerank"; diff --git a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/HuggingFaceServiceUpgradeIT.java b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/HuggingFaceServiceUpgradeIT.java index 61556e02e5cd2..41e67d7f39859 100644 --- a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/HuggingFaceServiceUpgradeIT.java +++ b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/HuggingFaceServiceUpgradeIT.java @@ -29,8 +29,9 @@ public class HuggingFaceServiceUpgradeIT extends InferenceUpgradeTestCase { - private static final String HF_EMBEDDINGS_ADDED = "8.12.0"; - private static final String HF_ELSER_ADDED = "8.12.0"; + // TODO: replace with proper test features + private static final String HF_EMBEDDINGS_TEST_FEATURE = "gte_v8.12.0"; + private static final String HF_ELSER_TEST_FEATURE = "gte_v8.12.0"; private static MockWebServer embeddingsServer; private static MockWebServer elserServer; @@ -56,10 +57,9 @@ public static void shutdown() { @SuppressWarnings("unchecked") public void testHFEmbeddings() throws IOException { - var embeddingsSupported = getOldClusterTestVersion().onOrAfter(HF_EMBEDDINGS_ADDED); - // `gte_v` indicates that the cluster version is Greater Than or Equal to MODELS_RENAMED_TO_ENDPOINTS - String oldClusterEndpointIdentifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("Hugging Face embedding service added in " + HF_EMBEDDINGS_ADDED, embeddingsSupported); + var embeddingsSupported = oldClusterHasFeature(HF_EMBEDDINGS_TEST_FEATURE); + String oldClusterEndpointIdentifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("Hugging Face embedding service supported", embeddingsSupported); final String oldClusterId = "old-cluster-embeddings"; final String upgradedClusterId = "upgraded-cluster-embeddings"; @@ -110,9 +110,9 @@ void assertEmbeddingInference(String inferenceId) throws IOException { @SuppressWarnings("unchecked") public void testElser() throws IOException { - var supported = getOldClusterTestVersion().onOrAfter(HF_ELSER_ADDED); - String old_cluster_endpoint_identifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("HF elser service added in " + HF_ELSER_ADDED, supported); + var supported = oldClusterHasFeature(HF_ELSER_TEST_FEATURE); + String old_cluster_endpoint_identifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("HF elser service supported", supported); final String oldClusterId = "old-cluster-elser"; final String upgradedClusterId = "upgraded-cluster-elser"; diff --git a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/InferenceUpgradeTestCase.java b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/InferenceUpgradeTestCase.java index bdbaedf532817..7b28c562b13fc 100644 --- a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/InferenceUpgradeTestCase.java +++ b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/InferenceUpgradeTestCase.java @@ -29,16 +29,18 @@ public class InferenceUpgradeTestCase extends ParameterizedRollingUpgradeTestCase { - static final String MODELS_RENAMED_TO_ENDPOINTS = "8.15.0"; + static final String MODELS_RENAMED_TO_ENDPOINTS_FEATURE = "gte_v8.15.0"; public InferenceUpgradeTestCase(@Name("upgradedNodes") int upgradedNodes) { super(upgradedNodes); } + // Note we need to use OLD_CLUSTER_VERSION directly here, as it may contain special values (e.g. 0.0.0) the ElasticsearchCluster + // builder uses to lookup a particular distribution @ClassRule public static ElasticsearchCluster cluster = ElasticsearchCluster.local() .distribution(DistributionType.DEFAULT) - .version(getOldClusterTestVersion()) + .version(OLD_CLUSTER_VERSION) .nodes(NODE_NUM) .setting("xpack.security.enabled", "false") .setting("xpack.license.self_generated.type", "trial") diff --git a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/OpenAiServiceUpgradeIT.java b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/OpenAiServiceUpgradeIT.java index b6dcfcb84a77f..0f6ac361dadf2 100644 --- a/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/OpenAiServiceUpgradeIT.java +++ b/x-pack/plugin/inference/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/application/OpenAiServiceUpgradeIT.java @@ -31,9 +31,10 @@ public class OpenAiServiceUpgradeIT extends InferenceUpgradeTestCase { - private static final String OPEN_AI_EMBEDDINGS_ADDED = "8.12.0"; - private static final String OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED = "8.13.0"; - private static final String OPEN_AI_COMPLETIONS_ADDED = "8.14.0"; + // TODO: replace with proper test features + private static final String OPEN_AI_EMBEDDINGS_TEST_FEATURE = "gte_v8.12.0"; + private static final String OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED_TEST_FEATURE = "gte_v8.13.0"; + private static final String OPEN_AI_COMPLETIONS_TEST_FEATURE = "gte_v8.14.0"; private static MockWebServer openAiEmbeddingsServer; private static MockWebServer openAiChatCompletionsServer; @@ -59,10 +60,9 @@ public static void shutdown() { @SuppressWarnings("unchecked") public void testOpenAiEmbeddings() throws IOException { - var openAiEmbeddingsSupported = getOldClusterTestVersion().onOrAfter(OPEN_AI_EMBEDDINGS_ADDED); - // `gte_v` indicates that the cluster version is Greater Than or Equal to MODELS_RENAMED_TO_ENDPOINTS - String oldClusterEndpointIdentifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("OpenAI embedding service added in " + OPEN_AI_EMBEDDINGS_ADDED, openAiEmbeddingsSupported); + var openAiEmbeddingsSupported = oldClusterHasFeature(OPEN_AI_EMBEDDINGS_TEST_FEATURE); + String oldClusterEndpointIdentifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("OpenAI embedding service supported", openAiEmbeddingsSupported); final String oldClusterId = "old-cluster-embeddings"; final String upgradedClusterId = "upgraded-cluster-embeddings"; @@ -86,7 +86,8 @@ public void testOpenAiEmbeddings() throws IOException { var serviceSettings = (Map) configs.get(0).get("service_settings"); var taskSettings = (Map) configs.get(0).get("task_settings"); var modelIdFound = serviceSettings.containsKey("model_id") - || (taskSettings.containsKey("model") && getOldClusterTestVersion().onOrBefore(OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED)); + || (taskSettings.containsKey("model") + && (oldClusterHasFeature(OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED_TEST_FEATURE) == false)); assertTrue("model_id not found in config: " + configs, modelIdFound); assertEmbeddingInference(oldClusterId); @@ -124,9 +125,9 @@ void assertEmbeddingInference(String inferenceId) throws IOException { @SuppressWarnings("unchecked") public void testOpenAiCompletions() throws IOException { - var openAiEmbeddingsSupported = getOldClusterTestVersion().onOrAfter(OPEN_AI_COMPLETIONS_ADDED); - String old_cluster_endpoint_identifier = oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) ? "endpoints" : "models"; - assumeTrue("OpenAI completions service added in " + OPEN_AI_COMPLETIONS_ADDED, openAiEmbeddingsSupported); + var openAiEmbeddingsSupported = oldClusterHasFeature(OPEN_AI_COMPLETIONS_TEST_FEATURE); + String old_cluster_endpoint_identifier = oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) ? "endpoints" : "models"; + assumeTrue("OpenAI completions service supported" + OPEN_AI_COMPLETIONS_TEST_FEATURE, openAiEmbeddingsSupported); final String oldClusterId = "old-cluster-completions"; final String upgradedClusterId = "upgraded-cluster-completions"; @@ -145,7 +146,7 @@ public void testOpenAiCompletions() throws IOException { List> configs = new LinkedList<>(); var request = get(testTaskType, oldClusterId); configs.addAll((Collection>) request.getOrDefault("endpoints", List.of())); - if (oldClusterHasFeature("gte_v" + MODELS_RENAMED_TO_ENDPOINTS) == false) { + if (oldClusterHasFeature(MODELS_RENAMED_TO_ENDPOINTS_FEATURE) == false) { configs.addAll((List>) request.getOrDefault(old_cluster_endpoint_identifier, List.of())); // in version 8.15, there was a breaking change where "models" was renamed to "endpoints" } @@ -186,7 +187,7 @@ void assertCompletionInference(String inferenceId) throws IOException { } private String oldClusterVersionCompatibleEmbeddingConfig() { - if (getOldClusterTestVersion().before(OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED)) { + if (oldClusterHasFeature(OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED_TEST_FEATURE) == false) { return embeddingConfigWithModelInTaskSettings(getUrl(openAiEmbeddingsServer)); } else { return embeddingConfigWithModelInServiceSettings(getUrl(openAiEmbeddingsServer)); From 41b201c0dd53dda0f8dba746ee23df63c12276c3 Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Wed, 11 Jun 2025 18:11:27 +0200 Subject: [PATCH 074/102] [Build] Build maven aggregation zip as part of DRA build (#129175) * [Build] Build maven aggregation zip as part of DRA build * Update path for aggregation zip --- .buildkite/scripts/dra-workflow.sh | 1 + build.gradle | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.buildkite/scripts/dra-workflow.sh b/.buildkite/scripts/dra-workflow.sh index 5e88a7be430af..c3473a645c74b 100755 --- a/.buildkite/scripts/dra-workflow.sh +++ b/.buildkite/scripts/dra-workflow.sh @@ -71,6 +71,7 @@ echo --- Building release artifacts buildReleaseArtifacts \ exportCompressedDockerImages \ exportDockerContexts \ + :zipAggregation \ :distribution:generateDependenciesReport PATH="$PATH:${JAVA_HOME}/bin" # Required by the following script diff --git a/build.gradle b/build.gradle index ed344b04a58f6..51158baf2e202 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,12 @@ nmcpAggregation { } tasks.named('zipAggregation').configure { + // put this in a place that works well with our DRA infrastructure + archiveFileName.unset(); + archiveBaseName.set("elasticsearch-maven-aggregation") + archiveVersion.set(VersionProperties.elasticsearch) + destinationDirectory.set(layout.buildDirectory.dir("distributions")); + dependsOn gradle.includedBuild('build-tools').task(':zipElasticPublication') from(zipTree(gradle.includedBuild('build-tools').task(':zipElasticPublication').resolveTask().archiveFile.get())) } From 92ab1465c098ac10f1cf421082c760e71e428c80 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Wed, 11 Jun 2025 19:20:57 +0300 Subject: [PATCH 075/102] Throttle indexing when disk IO throttling is disabled (#129245) The threadpool-based merge scheduler triggers indexing throttling if merges are still getting enqueued faster than they're executed, while they are also disk IO unthrottled. This PR fixes the case where indexing throttling was incorrectly NOT triggered when disk IO throttling was disabled via the index settings. --- docs/changelog/129245.yaml | 5 ++++ .../engine/ThreadPoolMergeScheduler.java | 4 +-- .../engine/ThreadPoolMergeSchedulerTests.java | 28 ++++++++++++++----- 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 docs/changelog/129245.yaml diff --git a/docs/changelog/129245.yaml b/docs/changelog/129245.yaml new file mode 100644 index 0000000000000..1a05e4340b4b3 --- /dev/null +++ b/docs/changelog/129245.yaml @@ -0,0 +1,5 @@ +pr: 129245 +summary: Throttle indexing when disk IO throttling is disabled +area: Engine +type: enhancement +issues: [] diff --git a/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java b/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java index 78a9695bea540..898fe078ef40c 100644 --- a/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java +++ b/server/src/main/java/org/elasticsearch/index/engine/ThreadPoolMergeScheduler.java @@ -244,8 +244,8 @@ private void checkMergeTaskThrottling() { // both currently running and enqueued merge tasks are considered "active" for throttling purposes int activeMerges = (int) (submittedMergesCount - doneMergesCount); if (activeMerges > configuredMaxMergeCount - // only throttle indexing if disk IO is un-throttled, and we still can't keep up with the merge load - && threadPoolMergeExecutorService.usingMaxTargetIORateBytesPerSec() + // only throttle indexing if disk IO is un-throttled (if enabled), and we still can't keep up with the merge load + && (config.isAutoThrottle() == false || threadPoolMergeExecutorService.usingMaxTargetIORateBytesPerSec()) && shouldThrottleIncomingMerges.get() == false) { // maybe enable merge task throttling synchronized (shouldThrottleIncomingMerges) { diff --git a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java index 156dcf581ec9c..92148a661ceab 100644 --- a/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java +++ b/server/src/test/java/org/elasticsearch/index/engine/ThreadPoolMergeSchedulerTests.java @@ -196,7 +196,15 @@ public void testSimpleMergeTaskReEnqueueingBySize() { } } - public void testIndexingThrottlingWhenSubmittingMerges() { + public void testIndexingThrottlingWhenSubmittingMergesWithDiskIOThrottlingEnabled() { + testIndexingThrottlingWhenSubmittingMerges(true); + } + + public void testIndexingThrottlingWhenSubmittingMergesWithDiskIOThrottlingDisabled() { + testIndexingThrottlingWhenSubmittingMerges(false); + } + + private void testIndexingThrottlingWhenSubmittingMerges(boolean withDiskIOThrottlingEnabled) { final int maxThreadCount = randomIntBetween(1, 5); // settings validation requires maxMergeCount >= maxThreadCount final int maxMergeCount = maxThreadCount + randomIntBetween(0, 5); @@ -209,6 +217,7 @@ public void testIndexingThrottlingWhenSubmittingMerges() { Settings mergeSchedulerSettings = Settings.builder() .put(MergeSchedulerConfig.MAX_THREAD_COUNT_SETTING.getKey(), maxThreadCount) .put(MergeSchedulerConfig.MAX_MERGE_COUNT_SETTING.getKey(), maxMergeCount) + .put(MergeSchedulerConfig.AUTO_THROTTLE_SETTING.getKey(), withDiskIOThrottlingEnabled) .build(); TestThreadPoolMergeScheduler threadPoolMergeScheduler = new TestThreadPoolMergeScheduler( new ShardId("index", "_na_", 1), @@ -224,12 +233,12 @@ public void testIndexingThrottlingWhenSubmittingMerges() { while (submittedMerges < mergesToSubmit - 1) { isUsingMaxTargetIORate.set(randomBoolean()); if (submittedMergeTasks.isEmpty() == false && randomBoolean()) { - // maybe schedule one submitted merge + // maybe schedule one of the submitted merges (but still it's not run) MergeTask mergeTask = randomFrom(submittedMergeTasks); submittedMergeTasks.remove(mergeTask); mergeTask.schedule(); } else { - // submit one merge + // submit one new merge MergeSource mergeSource = mock(MergeSource.class); OneMerge oneMerge = mock(OneMerge.class); when(oneMerge.getStoreMergeInfo()).thenReturn(getNewMergeInfo(randomLongBetween(1L, 10L))); @@ -237,7 +246,7 @@ public void testIndexingThrottlingWhenSubmittingMerges() { when(mergeSource.getNextMerge()).thenReturn(oneMerge, (OneMerge) null); threadPoolMergeScheduler.merge(mergeSource, randomFrom(MergeTrigger.values())); submittedMerges++; - if (isUsingMaxTargetIORate.get() && submittedMerges > maxMergeCount) { + if ((isUsingMaxTargetIORate.get() || withDiskIOThrottlingEnabled == false) && submittedMerges > maxMergeCount) { expectIndexThrottling = true; } else if (submittedMerges <= maxMergeCount) { expectIndexThrottling = false; @@ -246,15 +255,20 @@ public void testIndexingThrottlingWhenSubmittingMerges() { // assert IO throttle state assertThat(threadPoolMergeScheduler.isIndexingThrottlingEnabled(), is(expectIndexThrottling)); } - // submit one last merge when IO throttling is at max value - isUsingMaxTargetIORate.set(true); + if (withDiskIOThrottlingEnabled) { + // submit one last merge when IO throttling is at max value + isUsingMaxTargetIORate.set(true); + } else { + // but if disk IO throttling is not enabled, indexing throttling should still be triggered + isUsingMaxTargetIORate.set(randomBoolean()); + } MergeSource mergeSource = mock(MergeSource.class); OneMerge oneMerge = mock(OneMerge.class); when(oneMerge.getStoreMergeInfo()).thenReturn(getNewMergeInfo(randomLongBetween(1L, 10L))); when(oneMerge.getMergeProgress()).thenReturn(new MergePolicy.OneMergeProgress()); when(mergeSource.getNextMerge()).thenReturn(oneMerge, (OneMerge) null); threadPoolMergeScheduler.merge(mergeSource, randomFrom(MergeTrigger.values())); - // assert index throttling because IO throttling is at max value + // assert indexing throttling state because IO throttling is at max value OR disk IO throttling is disabled assertThat(threadPoolMergeScheduler.isIndexingThrottlingEnabled(), is(true)); } From c03317517df484255b3b7acf1189b9bd533e0a34 Mon Sep 17 00:00:00 2001 From: Kathleen DeRusso Date: Wed, 11 Jun 2025 13:01:24 -0400 Subject: [PATCH 076/102] Register match_phrase as a function not a snapshot function (#129255) * Register match_phrase as a function not a snapshot function * Update usage --- .../esql/kibana/definition/functions/match_phrase.json | 2 +- .../xpack/esql/expression/function/EsqlFunctionRegistry.java | 4 ++-- .../resources/rest-api-spec/test/esql/60_usage.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/match_phrase.json b/docs/reference/query-languages/esql/kibana/definition/functions/match_phrase.json index 128f11042ad0d..603aa08561d6c 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/match_phrase.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/match_phrase.json @@ -59,5 +59,5 @@ "FROM books\n| WHERE MATCH_PHRASE(author, \"William Faulkner\")" ], "preview" : true, - "snapshot_only" : true + "snapshot_only" : false } 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 ecc9dbd271327..b115eb5c33c6e 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 @@ -466,7 +466,8 @@ private static FunctionDefinition[][] functions() { def(Kql.class, uni(Kql::new), "kql"), def(Match.class, tri(Match::new), "match"), def(MultiMatch.class, MultiMatch::new, "multi_match"), - def(QueryString.class, bi(QueryString::new), "qstr") } }; + def(QueryString.class, bi(QueryString::new), "qstr"), + def(MatchPhrase.class, tri(MatchPhrase::new), "match_phrase") } }; } @@ -486,7 +487,6 @@ private static FunctionDefinition[][] snapshotFunctions() { def(LastOverTime.class, LastOverTime::withUnresolvedTimestamp, "last_over_time"), def(FirstOverTime.class, FirstOverTime::withUnresolvedTimestamp, "first_over_time"), def(Term.class, bi(Term::new), "term"), - def(MatchPhrase.class, tri(MatchPhrase::new), "match_phrase"), def(Knn.class, tri(Knn::new), "knn") } }; } 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 14cf02e0bdcb3..3aca6d2ff500b 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 @@ -130,7 +130,7 @@ setup: - match: {esql.functions.coalesce: $functions_coalesce} - gt: {esql.functions.categorize: $functions_categorize} # Testing for the entire function set isn't feasible, so we just check that we return the correct count as an approximation. - - length: {esql.functions: 157} # check the "sister" test below for a likely update to the same esql.functions length check + - length: {esql.functions: 156} # check the "sister" test below for a likely update to the same esql.functions length check --- "Basic ESQL usage output (telemetry) non-snapshot version": @@ -228,7 +228,7 @@ setup: - gt: {esql.functions.to_long: $functions_to_long} - match: {esql.functions.coalesce: $functions_coalesce} - gt: {esql.functions.categorize: $functions_categorize} - - length: {esql.functions: 145} # check the "sister" test above for a likely update to the same esql.functions length check + - length: {esql.functions: 146} # check the "sister" test above for a likely update to the same esql.functions length check --- took: From babfc8614c6e739dfb4268c48c395cd57d677deb Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Wed, 11 Jun 2025 20:22:13 +0200 Subject: [PATCH 077/102] [Gradle] Spotless plugin update (#115750) - provides better configuration cache support - requires some rework due to changed defaults --- build-tools-internal/build.gradle | 1 - gradle/build.versions.toml | 2 +- gradle/verification-metadata.xml | 211 ++++++++++++++++-- .../HuggingFaceRerankResponseEntityTests.java | 4 +- .../retention/MockWritableIndexExpander.java | 6 +- .../authc/AuthenticationServiceTests.java | 30 +-- 6 files changed, 213 insertions(+), 41 deletions(-) diff --git a/build-tools-internal/build.gradle b/build-tools-internal/build.gradle index c04ba9b90d5e7..11f035e225db1 100644 --- a/build-tools-internal/build.gradle +++ b/build-tools-internal/build.gradle @@ -381,7 +381,6 @@ tasks.named("jar") { spotless { java { - // workaround for https://github.com/diffplug/spotless/issues/2317 //toggleOffOn() target project.fileTree("src/main/java") { diff --git a/gradle/build.versions.toml b/gradle/build.versions.toml index bf0e6873f9073..07db2bb0116c8 100644 --- a/gradle/build.versions.toml +++ b/gradle/build.versions.toml @@ -45,7 +45,7 @@ snakeyaml = { group = "org.yaml", name = "snakeyaml", version = { strictly = "2. spock-core = { group = "org.spockframework", name="spock-core", version.ref="spock" } spock-junit4 = { group = "org.spockframework", name="spock-junit4", version.ref="spock" } spock-platform = { group = "org.spockframework", name="spock-bom", version.ref="spock" } -spotless-plugin = "com.diffplug.spotless:spotless-plugin-gradle:6.25.0" +spotless-plugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.4" wiremock = "com.github.tomakehurst:wiremock-jre8-standalone:2.23.2" xmlunit-core = "org.xmlunit:xmlunit-core:2.8.2" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index cf520c208cfd8..1ca6ceea3cc5a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -201,6 +201,11 @@ + + + + + @@ -211,34 +216,34 @@ - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -1423,6 +1428,11 @@ + + + + + @@ -1781,6 +1791,16 @@ + + + + + + + + + + @@ -2385,6 +2405,11 @@ + + + + + @@ -3626,6 +3651,16 @@ + + + + + + + + + + @@ -3636,6 +3671,16 @@ + + + + + + + + + + @@ -3746,6 +3791,11 @@ + + + + + @@ -3766,6 +3816,16 @@ + + + + + + + + + + @@ -3776,6 +3836,11 @@ + + + + + @@ -3796,6 +3861,11 @@ + + + + + @@ -3806,6 +3876,16 @@ + + + + + + + + + + @@ -3816,6 +3896,16 @@ + + + + + + + + + + @@ -3826,6 +3916,16 @@ + + + + + + + + + + @@ -3836,6 +3936,16 @@ + + + + + + + + + + @@ -3846,6 +3956,16 @@ + + + + + + + + + + @@ -3856,6 +3976,11 @@ + + + + + @@ -3866,6 +3991,16 @@ + + + + + + + + + + @@ -3876,6 +4011,16 @@ + + + + + + + + + + @@ -3886,6 +4031,16 @@ + + + + + + + + + + @@ -3901,6 +4056,16 @@ + + + + + + + + + + @@ -3911,6 +4076,16 @@ + + + + + + + + + + diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java index 5d1b14c46f099..ccea9663d4cfa 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java @@ -134,8 +134,8 @@ private void assertMissingFieldThrowsIllegalArgumentException(String responseJso assertThat(thrownException.getMessage(), is("Required [" + missingField + "]")); } - private void assertTopNLimit( - Integer topN, String responseJson, List expectation) throws IOException { + private void assertTopNLimit(Integer topN, String responseJson, List expectation) + throws IOException { when(REQUEST_MOCK.getTopN()).thenReturn(topN); RankedDocsResults parsedResults = HuggingFaceRerankResponseEntity.fromResponse( diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java index a2cf4479db0da..d32f84e2ded5c 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java @@ -30,10 +30,8 @@ public static WritableIndexExpander create(boolean stateIndexWritable) { } private static void mockWhenIndicesAreNotWritable(WritableIndexExpander writableIndexExpander) { - when(writableIndexExpander.getWritableIndices(anyString())) - .thenReturn(new ArrayList<>()); - when(writableIndexExpander.getWritableIndices(ArgumentMatchers.>any())) - .thenReturn(new ArrayList<>()); + when(writableIndexExpander.getWritableIndices(anyString())).thenReturn(new ArrayList<>()); + when(writableIndexExpander.getWritableIndices(ArgumentMatchers.>any())).thenReturn(new ArrayList<>()); } private static void mockWhenIndicesAreWritable(WritableIndexExpander writableIndexExpander) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 1b5bbd4de9a44..345bc3dbef362 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -791,7 +791,7 @@ public void testAuthenticateCached() throws Exception { public void testAuthenticateNonExistentRestRequestUserThrowsAuthenticationException() throws Exception { when(firstRealm.token(threadContext)).thenReturn( - new UsernamePasswordToken("idonotexist", new SecureString("passwd".toCharArray())) + new UsernamePasswordToken("idonotexist", new SecureString("passwd".toCharArray())) ); try { authenticateBlocking(restRequest, null); @@ -2069,23 +2069,23 @@ public void testExpiredToken() throws Exception { when(projectIndex.indexExists()).thenReturn(true); User user = new User("_username", "r1"); final Authentication expected = AuthenticationTestHelper.builder() - .user(user) - .realmRef(new RealmRef("realm", "custom", "node")) - .build(false); + .user(user) + .realmRef(new RealmRef("realm", "custom", "node")) + .build(false); PlainActionFuture tokenFuture = new PlainActionFuture<>(); Tuple newTokenBytes = tokenService.getRandomTokenBytes(randomBoolean()); try (ThreadContext.StoredContext ctx = threadContext.stashContext()) { Authentication originatingAuth = AuthenticationTestHelper.builder() - .user(new User("creator")) - .realmRef(new RealmRef("test", "test", "test")) - .build(false); + .user(new User("creator")) + .realmRef(new RealmRef("test", "test", "test")) + .build(false); tokenService.createOAuth2Tokens( - newTokenBytes.v1(), - newTokenBytes.v2(), - expected, - originatingAuth, - Collections.emptyMap(), - tokenFuture + newTokenBytes.v1(), + newTokenBytes.v2(), + expected, + originatingAuth, + Collections.emptyMap(), + tokenFuture ); } String token = tokenFuture.get().getAccessToken(); @@ -2104,8 +2104,8 @@ public void testExpiredToken() throws Exception { } threadContext.putHeader("Authorization", "Bearer " + token); ElasticsearchSecurityException e = expectThrows( - ElasticsearchSecurityException.class, - () -> authenticateBlocking("_action", transportRequest, null, null) + ElasticsearchSecurityException.class, + () -> authenticateBlocking("_action", transportRequest, null, null) ); if (requestIdAlreadyPresent) { assertThat(expectAuditRequestId(threadContext), is(reqId.get())); From 2726f507c361e82a25ac75e86fe25a7cedaef32b Mon Sep 17 00:00:00 2001 From: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:31:11 -0400 Subject: [PATCH 078/102] Adding support to exclude semantic_text subfields (#127664) * Adding support to exclude semantic_text subfields * Update docs/changelog/127664.yaml * Updating changelog file * remove duplicate test from yaml file * Adding support to exclude semantic_text subfields from mapper builders * Adding support for generic field types * refactoring to use builder and setting exclude value from semantic_text mapper * update in semantic_text mapper and fetcher to incorporate the support functionality * Fix code style issue * adding node feature for yaml tests * Adding more restrictive checks on yaml tests and few refactoring * Returns metadata fields from metadata mappers * returns all source fields for fieldcaps * gather all fields and iterate to process for fieldcaps api * revert back all changes from MappedFieldtype and subclasses * revert back exclude logic from semantic_text mapper * fix lint issues * fix lint issues * Adding runtime fields into fieldCaps * Fix linting issue * removing unused functions that used in previous implementation * fix multifield tests failure * getting alias fields for field caps * adding support for query time runtime fields * [CI] Auto commit changes from spotless * Fix empty mapping fieldCaps call * Address passthrough behavior for mappers * Fix SearchAsYoutype mapper failures * rename abstract method to have more meaningful name * Rename mapper function to match its functionality * Adding filtering for infernece subfields * revert back previous implementation changes * Adding yaml test for field caps not filtering multi-field * Fixing yaml test * Adding comment why .infernece filter is added --------- Co-authored-by: elasticsearchmachine Co-authored-by: Elastic Machine --- docs/changelog/127664.yaml | 5 ++ .../fieldcaps/FieldCapabilitiesFetcher.java | 10 +++ .../xpack/inference/InferenceFeatures.java | 4 +- .../mapper/SemanticTextFieldMapper.java | 3 + .../10_semantic_text_field_mapping.yml | 73 +++++++++++++++++++ .../10_semantic_text_field_mapping_bwc.yml | 23 ++++++ 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/127664.yaml diff --git a/docs/changelog/127664.yaml b/docs/changelog/127664.yaml new file mode 100644 index 0000000000000..6290607b987f7 --- /dev/null +++ b/docs/changelog/127664.yaml @@ -0,0 +1,5 @@ +pr: 127664 +summary: Exclude `semantic_text` subfields from field capabilities API +area: "Mapping" +type: enhancement +issues: [] diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesFetcher.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesFetcher.java index e6d7af11d06cc..dc73be9ed7559 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesFetcher.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesFetcher.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.fieldcaps; +import org.elasticsearch.cluster.metadata.InferenceFieldMetadata; import org.elasticsearch.cluster.metadata.MappingMetadata; import org.elasticsearch.core.Booleans; import org.elasticsearch.core.Nullable; @@ -30,6 +31,7 @@ import org.elasticsearch.tasks.CancellableTask; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -256,6 +258,14 @@ private static Predicate buildFilter(String[] filters, String[] Set acceptedTypes = Set.of(fieldTypes); fcf = ft -> acceptedTypes.contains(ft.familyTypeName()); } + + // Exclude internal ".inference" subfields of semantic_text fields from the field capabilities response + Collection inferenceFields = context.getMappingLookup().inferenceFields().values(); + for (InferenceFieldMetadata inferenceField : inferenceFields) { + Predicate next = ft -> ft.name().startsWith(inferenceField.getName() + ".inference") == false; + fcf = fcf == null ? next : fcf.and(next); + } + for (String filter : filters) { if ("parent".equals(filter) || "-parent".equals(filter)) { continue; 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 669e29ba7debf..d724bae4ecb64 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 @@ -15,6 +15,7 @@ import java.util.Set; +import static org.elasticsearch.xpack.inference.mapper.SemanticTextFieldMapper.SEMANTIC_TEXT_EXCLUDE_SUB_FIELDS_FROM_FIELD_CAPS; import static org.elasticsearch.xpack.inference.mapper.SemanticTextFieldMapper.SEMANTIC_TEXT_SUPPORT_CHUNKING_CONFIG; import static org.elasticsearch.xpack.inference.queries.SemanticKnnVectorQueryRewriteInterceptor.SEMANTIC_KNN_FILTER_FIX; import static org.elasticsearch.xpack.inference.queries.SemanticKnnVectorQueryRewriteInterceptor.SEMANTIC_KNN_VECTOR_QUERY_REWRITE_INTERCEPTION_SUPPORTED; @@ -59,7 +60,8 @@ public Set getTestFeatures() { SemanticTextFieldMapper.SEMANTIC_TEXT_HANDLE_EMPTY_INPUT, TEST_RULE_RETRIEVER_WITH_INDICES_THAT_DONT_RETURN_RANK_DOCS, SEMANTIC_TEXT_SUPPORT_CHUNKING_CONFIG, - SEMANTIC_TEXT_MATCH_ALL_HIGHLIGHTER + SEMANTIC_TEXT_MATCH_ALL_HIGHLIGHTER, + SEMANTIC_TEXT_EXCLUDE_SUB_FIELDS_FROM_FIELD_CAPS ); } } 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 d15414e34aef1..8e4f3ed4e67a8 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 @@ -134,6 +134,9 @@ public class SemanticTextFieldMapper extends FieldMapper implements InferenceFie public static final NodeFeature SEMANTIC_TEXT_SKIP_INFERENCE_FIELDS = new NodeFeature("semantic_text.skip_inference_fields"); public static final NodeFeature SEMANTIC_TEXT_BIT_VECTOR_SUPPORT = new NodeFeature("semantic_text.bit_vector_support"); public static final NodeFeature SEMANTIC_TEXT_SUPPORT_CHUNKING_CONFIG = new NodeFeature("semantic_text.support_chunking_config"); + public static final NodeFeature SEMANTIC_TEXT_EXCLUDE_SUB_FIELDS_FROM_FIELD_CAPS = new NodeFeature( + "semantic_text.exclude_sub_fields_from_field_caps" + ); public static final String CONTENT_TYPE = "semantic_text"; public static final String DEFAULT_ELSER_2_INFERENCE_ID = DEFAULT_ELSER_ID; diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping.yml index fcbeab9262b20..a1c2663b22cc9 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping.yml @@ -359,3 +359,76 @@ setup: index: test-always-include-inference-id-index - exists: test-always-include-inference-id-index.mappings.properties.semantic_field.inference_id + +--- +"Field caps exclude chunks and embedding fields": + - requires: + cluster_features: "semantic_text.exclude_sub_fields_from_field_caps" + reason: field caps api exclude semantic_text subfields from 9.1.0 & 8.19.0 + + - do: + field_caps: + include_empty_fields: true + index: test-index + fields: "*" + + - match: { indices: [ "test-index" ] } + - exists: fields.sparse_field + - exists: fields.dense_field + - not_exists: fields.sparse_field.inference.chunks.embeddings + - not_exists: fields.sparse_field.inference.chunks.offset + - not_exists: fields.sparse_field.inference.chunks + - not_exists: fields.sparse_field.inference + - not_exists: fields.dense_field.inference.chunks.embeddings + - not_exists: fields.dense_field.inference.chunks.offset + - not_exists: fields.dense_field.inference.chunks + - not_exists: fields.dense_field.inference + +--- +"Field caps does not exclude multi-fields under semantic_text": + - requires: + cluster_features: "semantic_text.exclude_sub_fields_from_field_caps" + reason: field caps api exclude semantic_text subfields from 9.1.0 & 8.19.0 + - do: + indices.create: + index: test-multi-field-index + body: + settings: + index: + mapping: + semantic_text: + use_legacy_format: false + mappings: + properties: + sparse_field: + type: semantic_text + inference_id: sparse-inference-id + fields: + sparse_keyword_field: + type: keyword + dense_field: + type: semantic_text + inference_id: dense-inference-id + fields: + dense_keyword_field: + type: keyword + + - do: + field_caps: + include_empty_fields: true + index: test-multi-field-index + fields: "*" + + - match: { indices: [ "test-multi-field-index" ] } + - exists: fields.sparse_field + - exists: fields.dense_field + - exists: fields.sparse_field\.sparse_keyword_field + - exists: fields.dense_field\.dense_keyword_field + - not_exists: fields.sparse_field.inference.chunks.embeddings + - not_exists: fields.sparse_field.inference.chunks.offset + - not_exists: fields.sparse_field.inference.chunks + - not_exists: fields.sparse_field.inference + - not_exists: fields.dense_field.inference.chunks.embeddings + - not_exists: fields.dense_field.inference.chunks.offset + - not_exists: fields.dense_field.inference.chunks + - not_exists: fields.dense_field.inference diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping_bwc.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping_bwc.yml index 7a0c5b912de26..fa935ac450f88 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping_bwc.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/10_semantic_text_field_mapping_bwc.yml @@ -307,3 +307,26 @@ setup: another_field: type: keyword +--- +"Field caps exclude chunks embedding and text fields": + - requires: + cluster_features: "semantic_text.exclude_sub_fields_from_field_caps" + reason: field caps api exclude semantic_text subfields from 9.1.0 & 8.19.0 + + - do: + field_caps: + include_empty_fields: true + index: test-index + fields: "*" + + - match: { indices: [ "test-index" ] } + - exists: fields.sparse_field + - exists: fields.dense_field + - not_exists: fields.sparse_field.inference.chunks.embeddings + - not_exists: fields.sparse_field.inference.chunks.text + - not_exists: fields.sparse_field.inference.chunks + - not_exists: fields.sparse_field.inference + - not_exists: fields.dense_field.inference.chunks.embeddings + - not_exists: fields.dense_field.inference.chunks.text + - not_exists: fields.dense_field.inference.chunks + - not_exists: fields.dense_field.inference From 73df4544462679d75aed6c43be4bbfe7ec036838 Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Wed, 11 Jun 2025 22:27:53 +0200 Subject: [PATCH 079/102] Revert "[Gradle] Spotless plugin update (#115750)" This reverts commit 6370d600b022fe73bbe6e9835316f2337d2b2d8c. --- build-tools-internal/build.gradle | 1 + gradle/build.versions.toml | 2 +- gradle/verification-metadata.xml | 211 ++---------------- .../HuggingFaceRerankResponseEntityTests.java | 4 +- .../retention/MockWritableIndexExpander.java | 6 +- .../authc/AuthenticationServiceTests.java | 30 +-- 6 files changed, 41 insertions(+), 213 deletions(-) diff --git a/build-tools-internal/build.gradle b/build-tools-internal/build.gradle index 11f035e225db1..c04ba9b90d5e7 100644 --- a/build-tools-internal/build.gradle +++ b/build-tools-internal/build.gradle @@ -381,6 +381,7 @@ tasks.named("jar") { spotless { java { + // workaround for https://github.com/diffplug/spotless/issues/2317 //toggleOffOn() target project.fileTree("src/main/java") { diff --git a/gradle/build.versions.toml b/gradle/build.versions.toml index 07db2bb0116c8..bf0e6873f9073 100644 --- a/gradle/build.versions.toml +++ b/gradle/build.versions.toml @@ -45,7 +45,7 @@ snakeyaml = { group = "org.yaml", name = "snakeyaml", version = { strictly = "2. spock-core = { group = "org.spockframework", name="spock-core", version.ref="spock" } spock-junit4 = { group = "org.spockframework", name="spock-junit4", version.ref="spock" } spock-platform = { group = "org.spockframework", name="spock-bom", version.ref="spock" } -spotless-plugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.4" +spotless-plugin = "com.diffplug.spotless:spotless-plugin-gradle:6.25.0" wiremock = "com.github.tomakehurst:wiremock-jre8-standalone:2.23.2" xmlunit-core = "org.xmlunit:xmlunit-core:2.8.2" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 1ca6ceea3cc5a..cf520c208cfd8 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -201,11 +201,6 @@ - - - - - @@ -216,34 +211,34 @@ - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -1428,11 +1423,6 @@ - - - - - @@ -1791,16 +1781,6 @@ - - - - - - - - - - @@ -2405,11 +2385,6 @@ - - - - - @@ -3651,16 +3626,6 @@ - - - - - - - - - - @@ -3671,16 +3636,6 @@ - - - - - - - - - - @@ -3791,11 +3746,6 @@ - - - - - @@ -3816,16 +3766,6 @@ - - - - - - - - - - @@ -3836,11 +3776,6 @@ - - - - - @@ -3861,11 +3796,6 @@ - - - - - @@ -3876,16 +3806,6 @@ - - - - - - - - - - @@ -3896,16 +3816,6 @@ - - - - - - - - - - @@ -3916,16 +3826,6 @@ - - - - - - - - - - @@ -3936,16 +3836,6 @@ - - - - - - - - - - @@ -3956,16 +3846,6 @@ - - - - - - - - - - @@ -3976,11 +3856,6 @@ - - - - - @@ -3991,16 +3866,6 @@ - - - - - - - - - - @@ -4011,16 +3876,6 @@ - - - - - - - - - - @@ -4031,16 +3886,6 @@ - - - - - - - - - - @@ -4056,16 +3901,6 @@ - - - - - - - - - - @@ -4076,16 +3911,6 @@ - - - - - - - - - - diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java index ccea9663d4cfa..5d1b14c46f099 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/response/HuggingFaceRerankResponseEntityTests.java @@ -134,8 +134,8 @@ private void assertMissingFieldThrowsIllegalArgumentException(String responseJso assertThat(thrownException.getMessage(), is("Required [" + missingField + "]")); } - private void assertTopNLimit(Integer topN, String responseJson, List expectation) - throws IOException { + private void assertTopNLimit( + Integer topN, String responseJson, List expectation) throws IOException { when(REQUEST_MOCK.getTopN()).thenReturn(topN); RankedDocsResults parsedResults = HuggingFaceRerankResponseEntity.fromResponse( diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java index d32f84e2ded5c..a2cf4479db0da 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/retention/MockWritableIndexExpander.java @@ -30,8 +30,10 @@ public static WritableIndexExpander create(boolean stateIndexWritable) { } private static void mockWhenIndicesAreNotWritable(WritableIndexExpander writableIndexExpander) { - when(writableIndexExpander.getWritableIndices(anyString())).thenReturn(new ArrayList<>()); - when(writableIndexExpander.getWritableIndices(ArgumentMatchers.>any())).thenReturn(new ArrayList<>()); + when(writableIndexExpander.getWritableIndices(anyString())) + .thenReturn(new ArrayList<>()); + when(writableIndexExpander.getWritableIndices(ArgumentMatchers.>any())) + .thenReturn(new ArrayList<>()); } private static void mockWhenIndicesAreWritable(WritableIndexExpander writableIndexExpander) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 345bc3dbef362..1b5bbd4de9a44 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -791,7 +791,7 @@ public void testAuthenticateCached() throws Exception { public void testAuthenticateNonExistentRestRequestUserThrowsAuthenticationException() throws Exception { when(firstRealm.token(threadContext)).thenReturn( - new UsernamePasswordToken("idonotexist", new SecureString("passwd".toCharArray())) + new UsernamePasswordToken("idonotexist", new SecureString("passwd".toCharArray())) ); try { authenticateBlocking(restRequest, null); @@ -2069,23 +2069,23 @@ public void testExpiredToken() throws Exception { when(projectIndex.indexExists()).thenReturn(true); User user = new User("_username", "r1"); final Authentication expected = AuthenticationTestHelper.builder() - .user(user) - .realmRef(new RealmRef("realm", "custom", "node")) - .build(false); + .user(user) + .realmRef(new RealmRef("realm", "custom", "node")) + .build(false); PlainActionFuture tokenFuture = new PlainActionFuture<>(); Tuple newTokenBytes = tokenService.getRandomTokenBytes(randomBoolean()); try (ThreadContext.StoredContext ctx = threadContext.stashContext()) { Authentication originatingAuth = AuthenticationTestHelper.builder() - .user(new User("creator")) - .realmRef(new RealmRef("test", "test", "test")) - .build(false); + .user(new User("creator")) + .realmRef(new RealmRef("test", "test", "test")) + .build(false); tokenService.createOAuth2Tokens( - newTokenBytes.v1(), - newTokenBytes.v2(), - expected, - originatingAuth, - Collections.emptyMap(), - tokenFuture + newTokenBytes.v1(), + newTokenBytes.v2(), + expected, + originatingAuth, + Collections.emptyMap(), + tokenFuture ); } String token = tokenFuture.get().getAccessToken(); @@ -2104,8 +2104,8 @@ public void testExpiredToken() throws Exception { } threadContext.putHeader("Authorization", "Bearer " + token); ElasticsearchSecurityException e = expectThrows( - ElasticsearchSecurityException.class, - () -> authenticateBlocking("_action", transportRequest, null, null) + ElasticsearchSecurityException.class, + () -> authenticateBlocking("_action", transportRequest, null, null) ); if (requestIdAlreadyPresent) { assertThat(expectAuditRequestId(threadContext), is(reqId.get())); From 4f01aaf33931e12c937bdd7fd06fe27a961f1b1c Mon Sep 17 00:00:00 2001 From: John Wagster Date: Wed, 11 Jun 2025 17:36:47 -0500 Subject: [PATCH 080/102] Switch IVF Writer to ES Logger (#129224) update to use ES logger instead of infostream and fixing native access warnings --- qa/vector/build.gradle | 5 ++ qa/vector/src/main/java/module-info.java | 1 + .../test/knn/KnnIndexTester.java | 75 +++++-------------- .../elasticsearch/test/knn/KnnIndexer.java | 12 +-- .../elasticsearch/test/knn/KnnSearcher.java | 2 +- .../vectors/DefaultIVFVectorsWriter.java | 50 +++++-------- .../ES814ScalarQuantizedVectorsFormat.java | 4 +- .../index/codec/vectors/IVFVectorsFormat.java | 1 - .../index/codec/vectors/IVFVectorsWriter.java | 6 -- 9 files changed, 51 insertions(+), 105 deletions(-) diff --git a/qa/vector/build.gradle b/qa/vector/build.gradle index 52b2eeae8226d..533b14363bee7 100644 --- a/qa/vector/build.gradle +++ b/qa/vector/build.gradle @@ -7,6 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import org.elasticsearch.gradle.internal.test.TestUtil + apply plugin: 'elasticsearch.java' apply plugin: 'elasticsearch.build' @@ -23,6 +25,8 @@ dependencies { api "org.apache.lucene:lucene-core:${versions.lucene}" api "org.apache.lucene:lucene-queries:${versions.lucene}" api "org.apache.lucene:lucene-codecs:${versions.lucene}" + implementation project(':libs:simdvec') + implementation project(':libs:native') implementation project(':libs:logging') implementation project(':server') } @@ -37,6 +41,7 @@ tasks.register("checkVec", JavaExec) { // Configure logging to console systemProperty "es.logger.out", "console" systemProperty "es.logger.level", "INFO" // Change to DEBUG if needed + systemProperty 'es.nativelibs.path', TestUtil.getTestLibraryPath(file("../../libs/native/libraries/build/platform/").toString()) if (buildParams.getRuntimeJavaVersion().map { it.majorVersion.toInteger() }.get() >= 21) { jvmArgs '-Xms4g', '-Xmx4g', '--add-modules=jdk.incubator.vector', '--enable-native-access=ALL-UNNAMED', '-Djava.util.concurrent.ForkJoinPool.common.parallelism=8', '-XX:+UnlockDiagnosticVMOptions', '-XX:+DebugNonSafepoints', '-XX:+HeapDumpOnOutOfMemoryError' diff --git a/qa/vector/src/main/java/module-info.java b/qa/vector/src/main/java/module-info.java index 9b9f585559688..b6647aafeb01f 100644 --- a/qa/vector/src/main/java/module-info.java +++ b/qa/vector/src/main/java/module-info.java @@ -11,6 +11,7 @@ requires org.elasticsearch.base; requires org.elasticsearch.server; requires org.elasticsearch.xcontent; + requires org.elasticsearch.cli; requires org.apache.lucene.core; requires org.apache.lucene.codecs; requires org.apache.lucene.queries; diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java index d3f707d191bc2..dcce1fd304b06 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java @@ -15,8 +15,10 @@ import org.apache.lucene.codecs.KnnVectorsFormat; import org.apache.lucene.codecs.lucene101.Lucene101Codec; import org.apache.lucene.codecs.lucene99.Lucene99HnswVectorsFormat; +import org.elasticsearch.cli.ProcessInfo; import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.LogConfigurator; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.PathUtils; import org.elasticsearch.index.codec.vectors.ES813Int8FlatVectorFormat; import org.elasticsearch.index.codec.vectors.ES814HnswScalarQuantizedVectorsFormat; @@ -24,6 +26,8 @@ import org.elasticsearch.index.codec.vectors.es818.ES818BinaryQuantizedVectorsFormat; import org.elasticsearch.index.codec.vectors.es818.ES818HnswBinaryQuantizedVectorsFormat; import org.elasticsearch.logging.Level; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xcontent.XContentParserConfiguration; import org.elasticsearch.xcontent.XContentType; @@ -35,19 +39,26 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; /** * A utility class to create and test KNN indices using Lucene. * It supports various index types (HNSW, FLAT, IVF) and configurations. */ public class KnnIndexTester { - static final Level LOG_LEVEL = Level.DEBUG; - - static final SysOutLogger logger = new SysOutLogger(); + static final Logger logger; static { LogConfigurator.loadLog4jPlugins(); - LogConfigurator.configureESLogging(); // native access requires logging to be initialized + + // necessary otherwise the es.logger.level system configuration in build.gradle is ignored + ProcessInfo pinfo = ProcessInfo.fromSystem(); + Map sysprops = pinfo.sysprops(); + String loggerLevel = sysprops.getOrDefault("es.logger.level", Level.INFO.name()); + Settings settings = Settings.builder().put("logger.level", loggerLevel).build(); + LogConfigurator.configureWithoutConfig(settings); + + logger = LogManager.getLogger(KnnIndexTester.class); } static final String INDEX_DIR = "target/knn_index"; @@ -163,7 +174,7 @@ public static void main(String[] args) throws Exception { FormattedResults formattedResults = new FormattedResults(); for (CmdLineArgs cmdLineArgs : cmdLineArgsList) { Results result = new Results(cmdLineArgs.indexType().name().toLowerCase(Locale.ROOT), cmdLineArgs.numDocs()); - System.out.println("Running KNN index tester with arguments: " + cmdLineArgs); + logger.info("Running KNN index tester with arguments: " + cmdLineArgs); Codec codec = createCodec(cmdLineArgs); Path indexPath = PathUtils.get(formatIndexPath(cmdLineArgs)); if (cmdLineArgs.reindex() || cmdLineArgs.forceMerge()) { @@ -195,8 +206,7 @@ public static void main(String[] args) throws Exception { } formattedResults.results.add(result); } - System.out.println("Results:"); - System.out.println(formattedResults); + logger.info("Results: \n" + formattedResults); } static class FormattedResults { @@ -326,57 +336,6 @@ static class Results { } } - static final class SysOutLogger { - - void warn(String message) { - if (LOG_LEVEL.ordinal() >= Level.WARN.ordinal()) { - System.out.println(message); - } - } - - void warn(String message, Object... params) { - if (LOG_LEVEL.ordinal() >= Level.WARN.ordinal()) { - System.out.println(String.format(Locale.ROOT, message, params)); - } - } - - void info(String message) { - if (LOG_LEVEL.ordinal() >= Level.INFO.ordinal()) { - System.out.println(message); - } - } - - void info(String message, Object... params) { - if (LOG_LEVEL.ordinal() >= Level.INFO.ordinal()) { - System.out.println(String.format(Locale.ROOT, message, params)); - } - } - - void debug(String message) { - if (LOG_LEVEL.ordinal() >= Level.DEBUG.ordinal()) { - System.out.println(message); - } - } - - void debug(String message, Object... params) { - if (LOG_LEVEL.ordinal() >= Level.DEBUG.ordinal()) { - System.out.println(String.format(Locale.ROOT, message, params)); - } - } - - void trace(String message) { - if (LOG_LEVEL == Level.TRACE) { - System.out.println(message); - } - } - - void trace(String message, Object... params) { - if (LOG_LEVEL == Level.TRACE) { - System.out.println(String.format(Locale.ROOT, message, params)); - } - } - } - static final class ThreadDetails { private static final ThreadMXBean threadBean = (ThreadMXBean) java.lang.management.ManagementFactory.getThreadMXBean(); public final long[] threadIDs; diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java index b6fc2ebd8b00e..f0c1648ae52dc 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexer.java @@ -117,7 +117,7 @@ public boolean isEnabled(String component) { } }); logger.debug( - "KnnIndexer: using codec=%s, vectorEncoding=%s, dim=%d, similarityFunction=%s", + "KnnIndexer: using codec={}, vectorEncoding={}, dim={}, similarityFunction={}", codec.getName(), vectorEncoding, dim, @@ -125,7 +125,7 @@ public boolean isEnabled(String component) { ); if (Files.exists(indexPath)) { - logger.debug("KnnIndexer: existing index at %s", indexPath); + logger.debug("KnnIndexer: existing index at {}", indexPath); } else { Files.createDirectories(indexPath); } @@ -143,7 +143,7 @@ public boolean isEnabled(String component) { ); } logger.info( - "docsPathSizeInBytes=%d, dim=%d, vectorEncoding=%s, byteSize=%d", + "docsPathSizeInBytes={}, dim={}, vectorEncoding={}, byteSize={}", docsPathSizeInBytes, dim, vectorEncoding, @@ -170,7 +170,7 @@ public boolean isEnabled(String component) { } long elapsed = System.nanoTime() - start; - logger.debug("Indexing took %d ms for %d docs", TimeUnit.NANOSECONDS.toMillis(elapsed), numDocs); + logger.debug("Indexing took {} ms for {} docs", TimeUnit.NANOSECONDS.toMillis(elapsed), numDocs); result.indexTimeMS = TimeUnit.NANOSECONDS.toMillis(elapsed); } @@ -183,14 +183,14 @@ public boolean isEnabled(String component) { } }); iwc.setCodec(codec); - logger.debug("KnnIndexer: forceMerge in %s", indexPath); + logger.debug("KnnIndexer: forceMerge in {}", indexPath); long startNS = System.nanoTime(); try (IndexWriter iw = new IndexWriter(FSDirectory.open(indexPath), iwc)) { iw.forceMerge(1); } long endNS = System.nanoTime(); long elapsedNSec = (endNS - startNS); - logger.info("forceMerge took %d ms", TimeUnit.NANOSECONDS.toMillis(elapsedNSec)); + logger.info("forceMerge took {} ms", TimeUnit.NANOSECONDS.toMillis(elapsedNSec)); results.forceMergeTimeMS = TimeUnit.NANOSECONDS.toMillis(elapsedNSec); } diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java index 5a951774ce22b..ec90dd46ef5b5 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnSearcher.java @@ -181,7 +181,7 @@ void runSearch(KnnIndexTester.Results finalResults) throws IOException { resultIds[i] = getResultIds(results[i], storedFields); } logger.info( - "completed %d searches in %d ms: %d QPS CPU time=%dms", + "completed {} searches in {} ms: {} QPS CPU time={}ms", numQueryVectors, elapsed, (1000L * numQueryVectors) / elapsed, diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java index 2527a91074e7e..cac8cc7cd2483 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/DefaultIVFVectorsWriter.java @@ -17,11 +17,12 @@ import org.apache.lucene.internal.hppc.IntArrayList; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; -import org.apache.lucene.util.InfoStream; import org.apache.lucene.util.VectorUtil; import org.apache.lucene.util.quantization.OptimizedScalarQuantizer; import org.elasticsearch.index.codec.vectors.cluster.HierarchicalKMeans; import org.elasticsearch.index.codec.vectors.cluster.KMeansResult; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; import org.elasticsearch.simdvec.ES91OSQVectorsScorer; import java.io.IOException; @@ -31,7 +32,6 @@ import static org.apache.lucene.codecs.lucene102.Lucene102BinaryQuantizedVectorsFormat.INDEX_BITS; import static org.apache.lucene.util.quantization.OptimizedScalarQuantizer.discretize; import static org.apache.lucene.util.quantization.OptimizedScalarQuantizer.packAsBinary; -import static org.elasticsearch.index.codec.vectors.IVFVectorsFormat.IVF_VECTOR_COMPONENT; /** * Default implementation of {@link IVFVectorsWriter}. It uses {@link HierarchicalKMeans} algorithm to @@ -39,6 +39,7 @@ * fashion. */ public class DefaultIVFVectorsWriter extends IVFVectorsWriter { + private static final Logger logger = LogManager.getLogger(DefaultIVFVectorsWriter.class); private final int vectorPerCluster; @@ -53,7 +54,6 @@ long[] buildAndWritePostingsLists( CentroidSupplier centroidSupplier, FloatVectorValues floatVectorValues, IndexOutput postingsOutput, - InfoStream infoStream, IntArrayList[] assignmentsByCluster ) throws IOException { // write the posting lists @@ -79,14 +79,14 @@ long[] buildAndWritePostingsLists( writePostingList(cluster, postingsOutput, binarizedByteVectorValues); } - if (infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - printClusterQualityStatistics(assignmentsByCluster, infoStream); + if (logger.isDebugEnabled()) { + printClusterQualityStatistics(assignmentsByCluster); } return offsets; } - private static void printClusterQualityStatistics(IntArrayList[] clusters, InfoStream infoStream) { + private static void printClusterQualityStatistics(IntArrayList[] clusters) { float min = Float.MAX_VALUE; float max = Float.MIN_VALUE; float mean = 0; @@ -105,20 +105,14 @@ private static void printClusterQualityStatistics(IntArrayList[] clusters, InfoS max = Math.max(max, cluster.size()); } float variance = m2 / (clusters.length - 1); - infoStream.message( - IVF_VECTOR_COMPONENT, - "Centroid count: " - + clusters.length - + " min: " - + min - + " max: " - + max - + " mean: " - + mean - + " stdDev: " - + Math.sqrt(variance) - + " variance: " - + variance + logger.debug( + "Centroid count: {} min: {} max: {} mean: {} stdDev: {} variance: {}", + clusters.length, + min, + max, + mean, + Math.sqrt(variance), + variance ); } @@ -208,17 +202,16 @@ CentroidAssignments calculateAndWriteCentroids( float[] globalCentroid ) throws IOException { // TODO: take advantage of prior generated clusters from mergeState in the future - return calculateAndWriteCentroids(fieldInfo, floatVectorValues, centroidOutput, mergeState.infoStream, globalCentroid, false); + return calculateAndWriteCentroids(fieldInfo, floatVectorValues, centroidOutput, globalCentroid, false); } CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, FloatVectorValues floatVectorValues, IndexOutput centroidOutput, - InfoStream infoStream, float[] globalCentroid ) throws IOException { - return calculateAndWriteCentroids(fieldInfo, floatVectorValues, centroidOutput, infoStream, globalCentroid, true); + return calculateAndWriteCentroids(fieldInfo, floatVectorValues, centroidOutput, globalCentroid, true); } /** @@ -228,7 +221,6 @@ CentroidAssignments calculateAndWriteCentroids( * @param fieldInfo merging field info * @param floatVectorValues the float vector values to merge * @param centroidOutput the centroid output - * @param infoStream the merge state * @param globalCentroid the global centroid, calculated by this method and used to quantize the centroids * @param cacheCentroids whether the centroids are kept or discarded once computed * @return the vector assignments, soar assignments, and if asked the centroids themselves that were computed @@ -238,7 +230,6 @@ CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, FloatVectorValues floatVectorValues, IndexOutput centroidOutput, - InfoStream infoStream, float[] globalCentroid, boolean cacheCentroids ) throws IOException { @@ -266,12 +257,9 @@ CentroidAssignments calculateAndWriteCentroids( // write centroids writeCentroids(centroids, fieldInfo, globalCentroid, centroidOutput); - if (infoStream.isEnabled(IVF_VECTOR_COMPONENT)) { - infoStream.message( - IVF_VECTOR_COMPONENT, - "calculate centroids and assign vectors time ms: " + ((System.nanoTime() - nanoTime) / 1000000.0) - ); - infoStream.message(IVF_VECTOR_COMPONENT, "final centroid count: " + centroids.length); + if (logger.isDebugEnabled()) { + logger.debug("calculate centroids and assign vectors time ms: {}", (System.nanoTime() - nanoTime) / 1000000.0); + logger.debug("final centroid count: {}", centroids.length); } IntArrayList[] assignmentsByCluster = new IntArrayList[centroids.length]; diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES814ScalarQuantizedVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES814ScalarQuantizedVectorsFormat.java index 84981c3d2a1be..3dde6fab00d4c 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/ES814ScalarQuantizedVectorsFormat.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/ES814ScalarQuantizedVectorsFormat.java @@ -242,8 +242,8 @@ static final class ESFlatVectorsScorer implements FlatVectorsScorer { final FlatVectorsScorer delegate; final VectorScorerFactory factory; - ESFlatVectorsScorer(FlatVectorsScorer delegte) { - this.delegate = delegte; + ESFlatVectorsScorer(FlatVectorsScorer delegate) { + this.delegate = delegate; factory = VectorScorerFactory.instance().orElse(null); } diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormat.java index 03e4a38b169de..d2495dfc89acd 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormat.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsFormat.java @@ -45,7 +45,6 @@ */ public class IVFVectorsFormat extends KnnVectorsFormat { - public static final String IVF_VECTOR_COMPONENT = "IVF"; public static final String NAME = "IVFVectorsFormat"; // centroid ordinals -> centroid values, offsets public static final String CENTROID_EXTENSION = "cenivf"; diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java index 73a0bdd5efa48..a29c21a158707 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/IVFVectorsWriter.java @@ -28,7 +28,6 @@ import org.apache.lucene.store.IOContext; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; -import org.apache.lucene.util.InfoStream; import org.apache.lucene.util.VectorUtil; import org.elasticsearch.core.IOUtils; import org.elasticsearch.core.SuppressForbidden; @@ -134,7 +133,6 @@ abstract CentroidAssignments calculateAndWriteCentroids( FieldInfo fieldInfo, FloatVectorValues floatVectorValues, IndexOutput centroidOutput, - InfoStream infoStream, float[] globalCentroid ) throws IOException; @@ -143,7 +141,6 @@ abstract long[] buildAndWritePostingsLists( CentroidSupplier centroidSupplier, FloatVectorValues floatVectorValues, IndexOutput postingsOutput, - InfoStream infoStream, IntArrayList[] assignmentsByCluster ) throws IOException; @@ -168,7 +165,6 @@ public final void flush(int maxDoc, Sorter.DocMap sortMap) throws IOException { fieldWriter.fieldInfo, floatVectorValues, ivfCentroids, - segmentWriteState.infoStream, globalCentroid ); @@ -180,7 +176,6 @@ public final void flush(int maxDoc, Sorter.DocMap sortMap) throws IOException { centroidSupplier, floatVectorValues, ivfClusters, - segmentWriteState.infoStream, centroidAssignments.assignmentsByCluster() ); // write posting lists @@ -313,7 +308,6 @@ public final void mergeOneField(FieldInfo fieldInfo, MergeState mergeState) thro centroidSupplier, floatVectorValues, ivfClusters, - mergeState.infoStream, centroidAssignments.assignmentsByCluster() ); assert offsets.length == centroidSupplier.size(); From 5295e8df09f4e79a5e93ac4795b1e212f0c22984 Mon Sep 17 00:00:00 2001 From: Nick Tindall Date: Thu, 12 Jun 2025 13:45:57 +1000 Subject: [PATCH 081/102] Add heap usage estimate to ClusterInfo (#128723) Co-authored-by: ywangd Co-authored-by: rjernst Relates: ES-11445 --- .../index/shard/IndexShardIT.java | 59 ++++++++++++- ...sticsearch.cluster.ShardHeapUsageCollector | 10 +++ .../org/elasticsearch/TransportVersions.java | 1 + .../elasticsearch/cluster/ClusterInfo.java | 29 ++++++- .../cluster/ClusterInfoSimulator.java | 1 + .../cluster/InternalClusterInfoService.java | 84 ++++++++++++++----- .../elasticsearch/cluster/ShardHeapUsage.java | 50 +++++++++++ .../cluster/ShardHeapUsageCollector.java | 36 ++++++++ .../node/NodeServiceProvider.java | 13 ++- .../cluster/ClusterInfoTests.java | 15 +++- ...rnalClusterInfoServiceSchedulingTests.java | 24 +++++- .../cluster/ShardHeapUsageTests.java | 37 ++++++++ .../ExpectedShardSizeEstimatorTests.java | 1 + .../AllocationStatsServiceTests.java | 1 + .../allocation/DiskThresholdMonitorTests.java | 2 +- .../ExpectedShardSizeAllocationTests.java | 3 +- .../BalancedShardsAllocatorTests.java | 3 +- .../ClusterAllocationSimulationTests.java | 2 +- .../allocator/ClusterBalanceStatsTests.java | 1 + .../allocator/ClusterInfoSimulatorTests.java | 10 ++- .../DesiredBalanceComputerTests.java | 4 +- .../DesiredBalanceReconcilerTests.java | 1 + .../decider/DiskThresholdDeciderTests.java | 2 +- .../DiskThresholdDeciderUnitTests.java | 14 +++- .../MockInternalClusterInfoService.java | 2 +- .../ReactiveStorageDeciderService.java | 1 + ...oscalingCalculateCapacityServiceTests.java | 4 +- .../FrozenStorageDeciderServiceTests.java | 2 +- .../ProactiveStorageDeciderServiceTests.java | 2 +- .../ReactiveStorageDeciderServiceTests.java | 4 +- ...nsportNodeDeprecationCheckActionTests.java | 1 + 31 files changed, 376 insertions(+), 43 deletions(-) create mode 100644 server/src/internalClusterTest/resources/META-INF/services/org.elasticsearch.cluster.ShardHeapUsageCollector create mode 100644 server/src/main/java/org/elasticsearch/cluster/ShardHeapUsage.java create mode 100644 server/src/main/java/org/elasticsearch/cluster/ShardHeapUsageCollector.java create mode 100644 server/src/test/java/org/elasticsearch/cluster/ShardHeapUsageTests.java diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java index 11e8b15432d35..4967888e021cf 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -10,6 +10,7 @@ import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.index.IndexRequest; @@ -19,6 +20,8 @@ import org.elasticsearch.cluster.ClusterInfoServiceUtils; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.InternalClusterInfoService; +import org.elasticsearch.cluster.ShardHeapUsage; +import org.elasticsearch.cluster.ShardHeapUsageCollector; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; @@ -62,6 +65,7 @@ import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.recovery.RecoveryState; +import org.elasticsearch.plugins.ClusterPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.test.DummyShardLock; @@ -82,6 +86,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; @@ -90,6 +95,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import static com.carrotsearch.randomizedtesting.RandomizedTest.randomAsciiLettersOfLength; @@ -111,12 +117,13 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.lessThanOrEqualTo; public class IndexShardIT extends ESSingleNodeTestCase { @Override protected Collection> getPlugins() { - return pluginList(InternalSettingsPlugin.class); + return pluginList(InternalSettingsPlugin.class, BogusShardHeapUsagePlugin.class); } public void testLockTryingToDelete() throws Exception { @@ -254,6 +261,20 @@ public void testExpectedShardSizeIsPresent() throws InterruptedException { assertThat(dataSetSize.get(), greaterThan(0L)); } + public void testHeapUsageEstimateIsPresent() { + InternalClusterInfoService clusterInfoService = (InternalClusterInfoService) getInstanceFromNode(ClusterInfoService.class); + ClusterInfoServiceUtils.refresh(clusterInfoService); + ClusterState state = getInstanceFromNode(ClusterService.class).state(); + Map shardHeapUsages = clusterInfoService.getClusterInfo().getShardHeapUsages(); + assertNotNull(shardHeapUsages); + assertEquals(state.nodes().size(), shardHeapUsages.size()); + for (DiscoveryNode node : state.nodes()) { + assertTrue(shardHeapUsages.containsKey(node.getId())); + ShardHeapUsage shardHeapUsage = shardHeapUsages.get(node.getId()); + assertThat(shardHeapUsage.estimatedFreeBytes(), lessThanOrEqualTo(shardHeapUsage.totalBytes())); + } + } + public void testIndexCanChangeCustomDataPath() throws Exception { final String index = "test-custom-data-path"; final Path sharedDataPath = getInstanceFromNode(Environment.class).sharedDataDir().resolve(randomAsciiLettersOfLength(10)); @@ -797,4 +818,40 @@ private static void assertAllIndicesRemovedAndDeletionCompleted(Iterable assertFalse(indicesService.hasUncompletedPendingDeletes()), 1, TimeUnit.MINUTES); } } + + public static class BogusShardShardHeapUsageCollector implements ShardHeapUsageCollector { + + private final BogusShardHeapUsagePlugin plugin; + + public BogusShardShardHeapUsageCollector(BogusShardHeapUsagePlugin plugin) { + this.plugin = plugin; + } + + @Override + public void collectClusterHeapUsage(ActionListener> listener) { + ActionListener.completeWith( + listener, + () -> plugin.getClusterService() + .state() + .nodes() + .stream() + .collect(Collectors.toUnmodifiableMap(DiscoveryNode::getId, node -> randomNonNegativeLong())) + ); + } + } + + public static class BogusShardHeapUsagePlugin extends Plugin implements ClusterPlugin { + + private final SetOnce clusterService = new SetOnce<>(); + + @Override + public Collection createComponents(PluginServices services) { + clusterService.set(services.clusterService()); + return List.of(); + } + + public ClusterService getClusterService() { + return clusterService.get(); + } + } } diff --git a/server/src/internalClusterTest/resources/META-INF/services/org.elasticsearch.cluster.ShardHeapUsageCollector b/server/src/internalClusterTest/resources/META-INF/services/org.elasticsearch.cluster.ShardHeapUsageCollector new file mode 100644 index 0000000000000..15b62c8240f25 --- /dev/null +++ b/server/src/internalClusterTest/resources/META-INF/services/org.elasticsearch.cluster.ShardHeapUsageCollector @@ -0,0 +1,10 @@ +# +# 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". +# + +org.elasticsearch.index.shard.IndexShardIT$BogusShardShardHeapUsageCollector diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index bca87f8dd8750..6e942cdc263e7 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -293,6 +293,7 @@ static TransportVersion def(int id) { public static final TransportVersion SNAPSHOT_INDEX_SHARD_STATUS_MISSING_STATS = def(9_093_0_00); public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK = def(9_094_0_00); public static final TransportVersion SEARCH_LOAD_PER_INDEX_STATS = def(9_095_0_00); + public static final TransportVersion HEAP_USAGE_IN_CLUSTER_INFO = def(9_096_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/cluster/ClusterInfo.java b/server/src/main/java/org/elasticsearch/cluster/ClusterInfo.java index a2c260e8699e8..460ed5e119c1a 100644 --- a/server/src/main/java/org/elasticsearch/cluster/ClusterInfo.java +++ b/server/src/main/java/org/elasticsearch/cluster/ClusterInfo.java @@ -57,9 +57,10 @@ public class ClusterInfo implements ChunkedToXContent, Writeable { final Map shardDataSetSizes; final Map dataPath; final Map reservedSpace; + final Map shardHeapUsages; protected ClusterInfo() { - this(Map.of(), Map.of(), Map.of(), Map.of(), Map.of(), Map.of()); + this(Map.of(), Map.of(), Map.of(), Map.of(), Map.of(), Map.of(), Map.of()); } /** @@ -71,6 +72,7 @@ protected ClusterInfo() { * @param shardDataSetSizes a shard id to data set size in bytes mapping per shard * @param dataPath the shard routing to datapath mapping * @param reservedSpace reserved space per shard broken down by node and data path + * @param shardHeapUsages shard heap usage broken down by node * @see #shardIdentifierFromRouting */ public ClusterInfo( @@ -79,7 +81,8 @@ public ClusterInfo( Map shardSizes, Map shardDataSetSizes, Map dataPath, - Map reservedSpace + Map reservedSpace, + Map shardHeapUsages ) { this.leastAvailableSpaceUsage = Map.copyOf(leastAvailableSpaceUsage); this.mostAvailableSpaceUsage = Map.copyOf(mostAvailableSpaceUsage); @@ -87,6 +90,7 @@ public ClusterInfo( this.shardDataSetSizes = Map.copyOf(shardDataSetSizes); this.dataPath = Map.copyOf(dataPath); this.reservedSpace = Map.copyOf(reservedSpace); + this.shardHeapUsages = Map.copyOf(shardHeapUsages); } public ClusterInfo(StreamInput in) throws IOException { @@ -98,6 +102,11 @@ public ClusterInfo(StreamInput in) throws IOException { ? in.readImmutableMap(NodeAndShard::new, StreamInput::readString) : in.readImmutableMap(nested -> NodeAndShard.from(new ShardRouting(nested)), StreamInput::readString); this.reservedSpace = in.readImmutableMap(NodeAndPath::new, ReservedSpace::new); + if (in.getTransportVersion().onOrAfter(TransportVersions.HEAP_USAGE_IN_CLUSTER_INFO)) { + this.shardHeapUsages = in.readImmutableMap(ShardHeapUsage::new); + } else { + this.shardHeapUsages = Map.of(); + } } @Override @@ -112,6 +121,9 @@ public void writeTo(StreamOutput out) throws IOException { out.writeMap(this.dataPath, (o, k) -> createFakeShardRoutingFromNodeAndShard(k).writeTo(o), StreamOutput::writeString); } out.writeMap(this.reservedSpace); + if (out.getTransportVersion().onOrAfter(TransportVersions.HEAP_USAGE_IN_CLUSTER_INFO)) { + out.writeMap(this.shardHeapUsages, StreamOutput::writeWriteable); + } } /** @@ -192,9 +204,22 @@ public Iterator toXContentChunked(ToXContent.Params params return builder.endObject(); // NodeAndPath }), endArray() // end "reserved_sizes" + // NOTE: We don't serialize shardHeapUsages at this stage, to avoid + // committing to API payloads until the feature is settled ); } + /** + * Returns a node id to estimated heap usage mapping for all nodes that we have such data for. + * Note that these estimates should be considered minimums. They may be used to determine whether + * there IS NOT capacity to do something, but not to determine that there IS capacity to do something. + * Also note that the map may not be complete, it may contain none, or a subset of the nodes in + * the cluster at any time. It may also contain entries for nodes that have since left the cluster. + */ + public Map getShardHeapUsages() { + return shardHeapUsages; + } + /** * Returns a node id to disk usage mapping for the path that has the least available space on the node. * Note that this does not take account of reserved space: there may be another path with less available _and unreserved_ space. diff --git a/server/src/main/java/org/elasticsearch/cluster/ClusterInfoSimulator.java b/server/src/main/java/org/elasticsearch/cluster/ClusterInfoSimulator.java index 17cf5c7b8b7c7..0536322b1d730 100644 --- a/server/src/main/java/org/elasticsearch/cluster/ClusterInfoSimulator.java +++ b/server/src/main/java/org/elasticsearch/cluster/ClusterInfoSimulator.java @@ -153,6 +153,7 @@ public ClusterInfo getClusterInfo() { shardSizes.toImmutableMap(), shardDataSetSizes, dataPath, + Map.of(), Map.of() ); } diff --git a/server/src/main/java/org/elasticsearch/cluster/InternalClusterInfoService.java b/server/src/main/java/org/elasticsearch/cluster/InternalClusterInfoService.java index 17bd913ffb724..4c8655118dd82 100644 --- a/server/src/main/java/org/elasticsearch/cluster/InternalClusterInfoService.java +++ b/server/src/main/java/org/elasticsearch/cluster/InternalClusterInfoService.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.core.TimeValue; import org.elasticsearch.index.shard.ShardId; @@ -82,12 +83,14 @@ public class InternalClusterInfoService implements ClusterInfoService, ClusterSt Property.NodeScope ); - private volatile boolean enabled; + private volatile boolean diskThresholdEnabled; private volatile TimeValue updateFrequency; private volatile TimeValue fetchTimeout; private volatile Map leastAvailableSpaceUsages; private volatile Map mostAvailableSpaceUsages; + private volatile Map maxHeapPerNode; + private volatile Map shardHeapUsagePerNode; private volatile IndicesStatsSummary indicesStatsSummary; private final ThreadPool threadPool; @@ -96,31 +99,41 @@ public class InternalClusterInfoService implements ClusterInfoService, ClusterSt private final Object mutex = new Object(); private final List> nextRefreshListeners = new ArrayList<>(); + private final ShardHeapUsageCollector shardHeapUsageCollector; private AsyncRefresh currentRefresh; private RefreshScheduler refreshScheduler; @SuppressWarnings("this-escape") - public InternalClusterInfoService(Settings settings, ClusterService clusterService, ThreadPool threadPool, Client client) { + public InternalClusterInfoService( + Settings settings, + ClusterService clusterService, + ThreadPool threadPool, + Client client, + ShardHeapUsageCollector shardHeapUsageCollector + ) { this.leastAvailableSpaceUsages = Map.of(); this.mostAvailableSpaceUsages = Map.of(); + this.maxHeapPerNode = Map.of(); + this.shardHeapUsagePerNode = Map.of(); this.indicesStatsSummary = IndicesStatsSummary.EMPTY; this.threadPool = threadPool; this.client = client; + this.shardHeapUsageCollector = shardHeapUsageCollector; this.updateFrequency = INTERNAL_CLUSTER_INFO_UPDATE_INTERVAL_SETTING.get(settings); this.fetchTimeout = INTERNAL_CLUSTER_INFO_TIMEOUT_SETTING.get(settings); - this.enabled = DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.get(settings); + this.diskThresholdEnabled = DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING.get(settings); ClusterSettings clusterSettings = clusterService.getClusterSettings(); clusterSettings.addSettingsUpdateConsumer(INTERNAL_CLUSTER_INFO_TIMEOUT_SETTING, this::setFetchTimeout); clusterSettings.addSettingsUpdateConsumer(INTERNAL_CLUSTER_INFO_UPDATE_INTERVAL_SETTING, this::setUpdateFrequency); clusterSettings.addSettingsUpdateConsumer( DiskThresholdSettings.CLUSTER_ROUTING_ALLOCATION_DISK_THRESHOLD_ENABLED_SETTING, - this::setEnabled + this::setDiskThresholdEnabled ); } - private void setEnabled(boolean enabled) { - this.enabled = enabled; + private void setDiskThresholdEnabled(boolean diskThresholdEnabled) { + this.diskThresholdEnabled = diskThresholdEnabled; } private void setFetchTimeout(TimeValue fetchTimeout) { @@ -169,27 +182,41 @@ private class AsyncRefresh { } void execute() { - if (enabled == false) { - logger.trace("skipping collecting info from cluster, notifying listeners with empty cluster info"); - leastAvailableSpaceUsages = Map.of(); - mostAvailableSpaceUsages = Map.of(); - indicesStatsSummary = IndicesStatsSummary.EMPTY; - callListeners(); - return; - } - logger.trace("starting async refresh"); try (var ignoredRefs = fetchRefs) { + if (diskThresholdEnabled) { + try (var ignored = threadPool.getThreadContext().clearTraceContext()) { + fetchIndicesStats(); + } + } else { + logger.trace("skipping collecting disk usage info from cluster, notifying listeners with empty cluster info"); + indicesStatsSummary = IndicesStatsSummary.EMPTY; + } try (var ignored = threadPool.getThreadContext().clearTraceContext()) { fetchNodeStats(); } try (var ignored = threadPool.getThreadContext().clearTraceContext()) { - fetchIndicesStats(); + fetchNodesHeapUsage(); } } } + private void fetchNodesHeapUsage() { + shardHeapUsageCollector.collectClusterHeapUsage(ActionListener.releaseAfter(new ActionListener<>() { + @Override + public void onResponse(Map currentShardHeapUsages) { + shardHeapUsagePerNode = currentShardHeapUsages; + } + + @Override + public void onFailure(Exception e) { + logger.warn("failed to fetch heap usage for nodes", e); + shardHeapUsagePerNode = Map.of(); + } + }, fetchRefs.acquire())); + } + private void fetchIndicesStats() { final IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest(); indicesStatsRequest.clear(); @@ -285,6 +312,7 @@ private void fetchNodeStats() { nodesStatsRequest.setIncludeShardsStats(false); nodesStatsRequest.clear(); nodesStatsRequest.addMetric(NodesStatsRequestParameters.Metric.FS); + nodesStatsRequest.addMetric(NodesStatsRequestParameters.Metric.JVM); nodesStatsRequest.setTimeout(fetchTimeout); client.admin().cluster().nodesStats(nodesStatsRequest, ActionListener.releaseAfter(new ActionListener<>() { @Override @@ -297,13 +325,16 @@ public void onResponse(NodesStatsResponse nodesStatsResponse) { Map leastAvailableUsagesBuilder = new HashMap<>(); Map mostAvailableUsagesBuilder = new HashMap<>(); - fillDiskUsagePerNode( + Map maxHeapPerNodeBuilder = new HashMap<>(); + processNodeStatsArray( adjustNodesStats(nodesStatsResponse.getNodes()), leastAvailableUsagesBuilder, - mostAvailableUsagesBuilder + mostAvailableUsagesBuilder, + maxHeapPerNodeBuilder ); leastAvailableSpaceUsages = Map.copyOf(leastAvailableUsagesBuilder); mostAvailableSpaceUsages = Map.copyOf(mostAvailableUsagesBuilder); + maxHeapPerNode = Map.copyOf(maxHeapPerNodeBuilder); } @Override @@ -315,6 +346,7 @@ public void onFailure(Exception e) { } leastAvailableSpaceUsages = Map.of(); mostAvailableSpaceUsages = Map.of(); + maxHeapPerNode = Map.of(); } }, fetchRefs.acquire())); } @@ -407,13 +439,21 @@ private boolean shouldRefresh() { @Override public ClusterInfo getClusterInfo() { final IndicesStatsSummary indicesStatsSummary = this.indicesStatsSummary; // single volatile read + final Map shardHeapUsages = new HashMap<>(); + maxHeapPerNode.forEach((nodeId, maxHeapSize) -> { + final Long estimatedHeapUsage = shardHeapUsagePerNode.get(nodeId); + if (estimatedHeapUsage != null) { + shardHeapUsages.put(nodeId, new ShardHeapUsage(nodeId, maxHeapSize.getBytes(), estimatedHeapUsage)); + } + }); return new ClusterInfo( leastAvailableSpaceUsages, mostAvailableSpaceUsages, indicesStatsSummary.shardSizes, indicesStatsSummary.shardDataSetSizes, indicesStatsSummary.dataPath, - indicesStatsSummary.reservedSpace + indicesStatsSummary.reservedSpace, + shardHeapUsages ); } @@ -476,10 +516,11 @@ static void buildShardLevelInfo( } } - private static void fillDiskUsagePerNode( + private static void processNodeStatsArray( List nodeStatsArray, Map newLeastAvailableUsages, - Map newMostAvailableUsages + Map newMostAvailableUsages, + Map maxHeapPerNodeBuilder ) { for (NodeStats nodeStats : nodeStatsArray) { DiskUsage leastAvailableUsage = DiskUsage.findLeastAvailablePath(nodeStats); @@ -490,6 +531,7 @@ private static void fillDiskUsagePerNode( if (mostAvailableUsage != null) { newMostAvailableUsages.put(nodeStats.getNode().getId(), mostAvailableUsage); } + maxHeapPerNodeBuilder.put(nodeStats.getNode().getId(), nodeStats.getJvm().getMem().getHeapMax()); } } diff --git a/server/src/main/java/org/elasticsearch/cluster/ShardHeapUsage.java b/server/src/main/java/org/elasticsearch/cluster/ShardHeapUsage.java new file mode 100644 index 0000000000000..3da97ac946f57 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/ShardHeapUsage.java @@ -0,0 +1,50 @@ +/* + * 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.cluster; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +import java.io.IOException; + +/** + * Record representing an estimate of the heap used by allocated shards and ongoing merges on a particular node + */ +public record ShardHeapUsage(String nodeId, long totalBytes, long estimatedUsageBytes) implements Writeable { + + public ShardHeapUsage { + assert totalBytes >= 0; + assert estimatedUsageBytes >= 0; + } + + public ShardHeapUsage(StreamInput in) throws IOException { + this(in.readString(), in.readVLong(), in.readVLong()); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(this.nodeId); + out.writeVLong(this.totalBytes); + out.writeVLong(this.estimatedUsageBytes); + } + + public long estimatedFreeBytes() { + return totalBytes - estimatedUsageBytes; + } + + public double estimatedFreeBytesAsPercentage() { + return 100.0 - estimatedUsageAsPercentage(); + } + + public double estimatedUsageAsPercentage() { + return 100.0 * estimatedUsageBytes / (double) totalBytes; + } +} diff --git a/server/src/main/java/org/elasticsearch/cluster/ShardHeapUsageCollector.java b/server/src/main/java/org/elasticsearch/cluster/ShardHeapUsageCollector.java new file mode 100644 index 0000000000000..c3f3213e035ad --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/ShardHeapUsageCollector.java @@ -0,0 +1,36 @@ +/* + * 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.cluster; + +import org.elasticsearch.action.ActionListener; + +import java.util.Map; + +/** + * Collect the shard heap usage for each node in the cluster. + *

+ * Results are returned as a map of node ID to estimated heap usage in bytes + * + * @see ShardHeapUsage + */ +public interface ShardHeapUsageCollector { + + /** + * This will be used when there is no ShardHeapUsageCollector available + */ + ShardHeapUsageCollector EMPTY = listener -> listener.onResponse(Map.of()); + + /** + * Collect the shard heap usage for every node in the cluster + * + * @param listener The listener which will receive the results + */ + void collectClusterHeapUsage(ActionListener> listener); +} diff --git a/server/src/main/java/org/elasticsearch/node/NodeServiceProvider.java b/server/src/main/java/org/elasticsearch/node/NodeServiceProvider.java index 1c3f19b20b4dc..8ef81eb35543a 100644 --- a/server/src/main/java/org/elasticsearch/node/NodeServiceProvider.java +++ b/server/src/main/java/org/elasticsearch/node/NodeServiceProvider.java @@ -13,6 +13,7 @@ import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.cluster.ClusterInfoService; import org.elasticsearch.cluster.InternalClusterInfoService; +import org.elasticsearch.cluster.ShardHeapUsageCollector; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.breaker.CircuitBreaker; @@ -74,7 +75,17 @@ ClusterInfoService newClusterInfoService( ThreadPool threadPool, NodeClient client ) { - final InternalClusterInfoService service = new InternalClusterInfoService(settings, clusterService, threadPool, client); + final ShardHeapUsageCollector shardHeapUsageCollector = pluginsService.loadSingletonServiceProvider( + ShardHeapUsageCollector.class, + () -> ShardHeapUsageCollector.EMPTY + ); + final InternalClusterInfoService service = new InternalClusterInfoService( + settings, + clusterService, + threadPool, + client, + shardHeapUsageCollector + ); if (DiscoveryNode.isMasterNode(settings)) { // listen for state changes (this node starts/stops being the elected master, or new nodes are added) clusterService.addListener(service); diff --git a/server/src/test/java/org/elasticsearch/cluster/ClusterInfoTests.java b/server/src/test/java/org/elasticsearch/cluster/ClusterInfoTests.java index 70adfa61dd853..532f9b649fbf4 100644 --- a/server/src/test/java/org/elasticsearch/cluster/ClusterInfoTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/ClusterInfoTests.java @@ -41,10 +41,23 @@ public static ClusterInfo randomClusterInfo() { randomShardSizes(), randomDataSetSizes(), randomRoutingToDataPath(), - randomReservedSpace() + randomReservedSpace(), + randomNodeHeapUsage() ); } + private static Map randomNodeHeapUsage() { + int numEntries = randomIntBetween(0, 128); + Map nodeHeapUsage = new HashMap<>(numEntries); + for (int i = 0; i < numEntries; i++) { + String key = randomAlphaOfLength(32); + final int totalBytes = randomIntBetween(0, Integer.MAX_VALUE); + final ShardHeapUsage shardHeapUsage = new ShardHeapUsage(randomAlphaOfLength(4), totalBytes, randomIntBetween(0, totalBytes)); + nodeHeapUsage.put(key, shardHeapUsage); + } + return nodeHeapUsage; + } + private static Map randomDiskUsage() { int numEntries = randomIntBetween(0, 128); Map builder = new HashMap<>(numEntries); diff --git a/server/src/test/java/org/elasticsearch/cluster/InternalClusterInfoServiceSchedulingTests.java b/server/src/test/java/org/elasticsearch/cluster/InternalClusterInfoServiceSchedulingTests.java index bcf5be82f6da7..67a745e743b04 100644 --- a/server/src/test/java/org/elasticsearch/cluster/InternalClusterInfoServiceSchedulingTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/InternalClusterInfoServiceSchedulingTests.java @@ -34,12 +34,17 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.client.NoOpClient; import org.elasticsearch.threadpool.ThreadPool; +import org.mockito.Mockito; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import static org.elasticsearch.cluster.InternalClusterInfoService.INTERNAL_CLUSTER_INFO_UPDATE_INTERVAL_SETTING; import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; public class InternalClusterInfoServiceSchedulingTests extends ESTestCase { @@ -71,7 +76,14 @@ protected PrioritizedEsThreadPoolExecutor createThreadPoolExecutor() { final ClusterService clusterService = new ClusterService(settings, clusterSettings, masterService, clusterApplierService); final FakeClusterInfoServiceClient client = new FakeClusterInfoServiceClient(threadPool); - final InternalClusterInfoService clusterInfoService = new InternalClusterInfoService(settings, clusterService, threadPool, client); + final ShardHeapUsageCollector mockShardHeapUsageCollector = spy(new StubShardShardHeapUsageCollector()); + final InternalClusterInfoService clusterInfoService = new InternalClusterInfoService( + settings, + clusterService, + threadPool, + client, + mockShardHeapUsageCollector + ); clusterService.addListener(clusterInfoService); clusterInfoService.addListener(ignored -> {}); @@ -107,11 +119,13 @@ protected PrioritizedEsThreadPoolExecutor createThreadPoolExecutor() { deterministicTaskQueue.runAllRunnableTasks(); for (int i = 0; i < 3; i++) { + Mockito.clearInvocations(mockShardHeapUsageCollector); final int initialRequestCount = client.requestCount; final long duration = INTERNAL_CLUSTER_INFO_UPDATE_INTERVAL_SETTING.get(settings).millis(); runFor(deterministicTaskQueue, duration); deterministicTaskQueue.runAllRunnableTasks(); assertThat(client.requestCount, equalTo(initialRequestCount + 2)); // should have run two client requests per interval + verify(mockShardHeapUsageCollector).collectClusterHeapUsage(any()); // Should poll for heap usage once per interval } final AtomicBoolean failMaster2 = new AtomicBoolean(); @@ -128,6 +142,14 @@ protected PrioritizedEsThreadPoolExecutor createThreadPoolExecutor() { assertFalse(deterministicTaskQueue.hasDeferredTasks()); } + private static class StubShardShardHeapUsageCollector implements ShardHeapUsageCollector { + + @Override + public void collectClusterHeapUsage(ActionListener> listener) { + listener.onResponse(Map.of()); + } + } + private static void runFor(DeterministicTaskQueue deterministicTaskQueue, long duration) { final long endTime = deterministicTaskQueue.getCurrentTimeMillis() + duration; while (deterministicTaskQueue.getCurrentTimeMillis() < endTime diff --git a/server/src/test/java/org/elasticsearch/cluster/ShardHeapUsageTests.java b/server/src/test/java/org/elasticsearch/cluster/ShardHeapUsageTests.java new file mode 100644 index 0000000000000..f41cc8fafd887 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/cluster/ShardHeapUsageTests.java @@ -0,0 +1,37 @@ +/* + * 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.cluster; + +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; + +public class ShardHeapUsageTests extends ESTestCase { + + public void testEstimatedUsageAsPercentage() { + final long totalBytes = randomNonNegativeLong(); + final long estimatedUsageBytes = randomLongBetween(0, totalBytes); + final ShardHeapUsage shardHeapUsage = new ShardHeapUsage(randomUUID(), totalBytes, estimatedUsageBytes); + assertThat(shardHeapUsage.estimatedFreeBytesAsPercentage(), greaterThanOrEqualTo(0.0)); + assertThat(shardHeapUsage.estimatedFreeBytesAsPercentage(), lessThanOrEqualTo(100.0)); + assertEquals(shardHeapUsage.estimatedUsageAsPercentage(), 100.0 * estimatedUsageBytes / totalBytes, 0.0001); + } + + public void testEstimatedFreeBytesAsPercentage() { + final long totalBytes = randomNonNegativeLong(); + final long estimatedUsageBytes = randomLongBetween(0, totalBytes); + final long estimatedFreeBytes = totalBytes - estimatedUsageBytes; + final ShardHeapUsage shardHeapUsage = new ShardHeapUsage(randomUUID(), totalBytes, estimatedUsageBytes); + assertThat(shardHeapUsage.estimatedFreeBytesAsPercentage(), greaterThanOrEqualTo(0.0)); + assertThat(shardHeapUsage.estimatedFreeBytesAsPercentage(), lessThanOrEqualTo(100.0)); + assertEquals(shardHeapUsage.estimatedFreeBytesAsPercentage(), 100.0 * estimatedFreeBytes / totalBytes, 0.0001); + } +} diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/ExpectedShardSizeEstimatorTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/ExpectedShardSizeEstimatorTests.java index a2e2f3326f527..754b4d2b22d0d 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/ExpectedShardSizeEstimatorTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/ExpectedShardSizeEstimatorTests.java @@ -205,6 +205,7 @@ private static ClusterInfo createClusterInfo(ShardRouting shard, Long size) { Map.of(ClusterInfo.shardIdentifierFromRouting(shard), size), Map.of(), Map.of(), + Map.of(), Map.of() ); } diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/AllocationStatsServiceTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/AllocationStatsServiceTests.java index 4a07b837b08af..14e0aaa253749 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/AllocationStatsServiceTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/AllocationStatsServiceTests.java @@ -78,6 +78,7 @@ public void testShardStats() { Map.of(ClusterInfo.shardIdentifierFromRouting(shardId, true), currentShardSize), Map.of(), Map.of(), + Map.of(), Map.of() ); diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DiskThresholdMonitorTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DiskThresholdMonitorTests.java index 6ce417456d30a..df0fa875a7249 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DiskThresholdMonitorTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/DiskThresholdMonitorTests.java @@ -1580,7 +1580,7 @@ private static ClusterInfo clusterInfo( Map diskUsages, Map reservedSpace ) { - return new ClusterInfo(diskUsages, Map.of(), Map.of(), Map.of(), Map.of(), reservedSpace); + return new ClusterInfo(diskUsages, Map.of(), Map.of(), Map.of(), Map.of(), reservedSpace, Map.of()); } private static DiscoveryNode newFrozenOnlyNode(String nodeId) { diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ExpectedShardSizeAllocationTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ExpectedShardSizeAllocationTests.java index cea6b24684978..f1a2b4b1358fe 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ExpectedShardSizeAllocationTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ExpectedShardSizeAllocationTests.java @@ -258,11 +258,12 @@ private static ClusterInfo createClusterInfoWith(ShardId shardId, long size) { ), Map.of(), Map.of(), + Map.of(), Map.of() ); } private static ClusterInfo createClusterInfo(Map diskUsage, Map shardSizes) { - return new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), Map.of(), Map.of()); + return new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), Map.of(), Map.of(), Map.of()); } } diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/BalancedShardsAllocatorTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/BalancedShardsAllocatorTests.java index 7e05ae7c57f79..8ab031aa53fe1 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/BalancedShardsAllocatorTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/BalancedShardsAllocatorTests.java @@ -608,6 +608,7 @@ public void testShardSizeDiscrepancyWithinIndex() { ), Map.of(), Map.of(), + Map.of(), Map.of() ) ); @@ -704,7 +705,7 @@ private RoutingAllocation createRoutingAllocation(ClusterState clusterState) { } private static ClusterInfo createClusterInfo(Map indexSizes) { - return new ClusterInfo(Map.of(), Map.of(), indexSizes, Map.of(), Map.of(), Map.of()); + return new ClusterInfo(Map.of(), Map.of(), indexSizes, Map.of(), Map.of(), Map.of(), Map.of()); } private static IndexMetadata.Builder anIndex(String name) { diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterAllocationSimulationTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterAllocationSimulationTests.java index c75912fda27e3..277521c5832a1 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterAllocationSimulationTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterAllocationSimulationTests.java @@ -561,7 +561,7 @@ public ClusterInfo getClusterInfo() { dataPath.put(new ClusterInfo.NodeAndShard(shardRouting.currentNodeId(), shardRouting.shardId()), "/data"); } - return new ClusterInfo(diskSpaceUsage, diskSpaceUsage, shardSizes, Map.of(), dataPath, Map.of()); + return new ClusterInfo(diskSpaceUsage, diskSpaceUsage, shardSizes, Map.of(), dataPath, Map.of(), Map.of()); } } diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterBalanceStatsTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterBalanceStatsTests.java index 6adb9f1e8ad26..80fe603488fd3 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterBalanceStatsTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterBalanceStatsTests.java @@ -345,6 +345,7 @@ private ClusterInfo createClusterInfo(List> shardSizes) { .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)), Map.of(), Map.of(), + Map.of(), Map.of() ); } diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterInfoSimulatorTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterInfoSimulatorTests.java index cdb8c5f602032..b67e248999ced 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterInfoSimulatorTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/ClusterInfoSimulatorTests.java @@ -690,7 +690,15 @@ public ClusterInfoTestBuilder withReservedSpace(String nodeId, String path, long } public ClusterInfo build() { - return new ClusterInfo(leastAvailableSpaceUsage, mostAvailableSpaceUsage, shardSizes, Map.of(), Map.of(), reservedSpace); + return new ClusterInfo( + leastAvailableSpaceUsage, + mostAvailableSpaceUsage, + shardSizes, + Map.of(), + Map.of(), + reservedSpace, + Map.of() + ); } } diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceComputerTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceComputerTests.java index 6e496b85ede97..a0d28ce124584 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceComputerTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceComputerTests.java @@ -690,7 +690,7 @@ public void testDesiredBalanceShouldConvergeInABigCluster() { .stream() .collect(toMap(Map.Entry::getKey, it -> new DiskUsage(it.getKey(), it.getKey(), "/data", diskSize, diskSize - it.getValue()))); - var clusterInfo = new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), dataPath, Map.of()); + var clusterInfo = new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), dataPath, Map.of(), Map.of()); var settings = Settings.EMPTY; @@ -1196,7 +1196,7 @@ public ClusterInfoTestBuilder withReservedSpace(String nodeId, long size, ShardI } public ClusterInfo build() { - return new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), Map.of(), reservedSpace); + return new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), Map.of(), reservedSpace, Map.of()); } } diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerTests.java index 4eed552d5f1af..844912cba4c17 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/allocator/DesiredBalanceReconcilerTests.java @@ -619,6 +619,7 @@ public void testUnassignedAllocationPredictsDiskUsage() { shardSizesBuilder.build(), ImmutableOpenMap.of(), ImmutableOpenMap.of(), + ImmutableOpenMap.of(), ImmutableOpenMap.of() ); diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderTests.java index 9522629de8f0d..5467d313834b8 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderTests.java @@ -1406,7 +1406,7 @@ static class DevNullClusterInfo extends ClusterInfo { Map shardSizes, Map reservedSpace ) { - super(leastAvailableSpaceUsage, mostAvailableSpaceUsage, shardSizes, Map.of(), Map.of(), reservedSpace); + super(leastAvailableSpaceUsage, mostAvailableSpaceUsage, shardSizes, Map.of(), Map.of(), reservedSpace, Map.of()); } @Override diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderUnitTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderUnitTests.java index d8cb13d7a7ba2..7da75f61da801 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderUnitTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/DiskThresholdDeciderUnitTests.java @@ -108,6 +108,7 @@ public void testCanAllocateUsesMaxAvailableSpace() { Map.of("[test][0][p]", 10L), // 10 bytes, Map.of(), Map.of(), + Map.of(), Map.of() ); RoutingAllocation allocation = new RoutingAllocation( @@ -179,6 +180,7 @@ private void doTestCannotAllocateDueToLackOfDiskResources(boolean testMaxHeadroo Map.of("[test][0][p]", shardSize), Map.of(), Map.of(), + Map.of(), Map.of() ); RoutingAllocation allocation = new RoutingAllocation( @@ -324,6 +326,7 @@ private void doTestCanRemainUsesLeastAvailableSpace(boolean testMaxHeadroom) { shardSizes, Map.of(), shardRoutingMap, + Map.of(), Map.of() ); RoutingAllocation allocation = new RoutingAllocation( @@ -843,6 +846,7 @@ public void testDecidesYesIfWatermarksIgnored() { Map.of("[test][0][p]", 10L), Map.of(), Map.of(), + Map.of(), Map.of() ); RoutingAllocation allocation = new RoutingAllocation( @@ -904,7 +908,15 @@ public void testCannotForceAllocateOver100PercentUsage() { // bigger than available space final long shardSize = randomIntBetween(1, 10); shardSizes.put("[test][0][p]", shardSize); - ClusterInfo clusterInfo = new ClusterInfo(leastAvailableUsages, mostAvailableUsage, shardSizes, Map.of(), Map.of(), Map.of()); + ClusterInfo clusterInfo = new ClusterInfo( + leastAvailableUsages, + mostAvailableUsage, + shardSizes, + Map.of(), + Map.of(), + Map.of(), + Map.of() + ); RoutingAllocation allocation = new RoutingAllocation( new AllocationDeciders(Collections.singleton(decider)), clusterState, diff --git a/test/framework/src/main/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java b/test/framework/src/main/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java index 49a36f64e2811..64cd81e1a9ab2 100644 --- a/test/framework/src/main/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java +++ b/test/framework/src/main/java/org/elasticsearch/cluster/MockInternalClusterInfoService.java @@ -43,7 +43,7 @@ public static class TestPlugin extends Plugin {} private volatile BiFunction diskUsageFunction; public MockInternalClusterInfoService(Settings settings, ClusterService clusterService, ThreadPool threadPool, NodeClient client) { - super(settings, clusterService, threadPool, client); + super(settings, clusterService, threadPool, client, ShardHeapUsageCollector.EMPTY); } public void setDiskUsageFunctionAndRefresh(BiFunction diskUsageFn) { diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderService.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderService.java index 77ba019835ec9..e451b1d45817d 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderService.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderService.java @@ -959,6 +959,7 @@ private ExtendedClusterInfo(Map extraShardSizes, ClusterInfo info) extraShardSizes, Map.of(), Map.of(), + Map.of(), Map.of() ); this.delegate = info; diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/capacity/AutoscalingCalculateCapacityServiceTests.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/capacity/AutoscalingCalculateCapacityServiceTests.java index 4061d37832184..12f7dde103c9c 100644 --- a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/capacity/AutoscalingCalculateCapacityServiceTests.java +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/capacity/AutoscalingCalculateCapacityServiceTests.java @@ -262,7 +262,7 @@ public void testContext() { } } state = ClusterState.builder(ClusterName.DEFAULT).nodes(nodes).build(); - info = new ClusterInfo(leastUsages, mostUsages, Map.of(), Map.of(), Map.of(), Map.of()); + info = new ClusterInfo(leastUsages, mostUsages, Map.of(), Map.of(), Map.of(), Map.of(), Map.of()); context = new AutoscalingCalculateCapacityService.DefaultAutoscalingDeciderContext( roleNames, state, @@ -311,7 +311,7 @@ public void testContext() { ) ); - info = new ClusterInfo(leastUsages, mostUsages, Map.of(), Map.of(), Map.of(), Map.of()); + info = new ClusterInfo(leastUsages, mostUsages, Map.of(), Map.of(), Map.of(), Map.of(), Map.of()); context = new AutoscalingCalculateCapacityService.DefaultAutoscalingDeciderContext( roleNames, state, diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/FrozenStorageDeciderServiceTests.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/FrozenStorageDeciderServiceTests.java index ab09f20e34397..37295ebf44208 100644 --- a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/FrozenStorageDeciderServiceTests.java +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/FrozenStorageDeciderServiceTests.java @@ -109,7 +109,7 @@ public Tuple sizeAndClusterInfo(IndexMetadata indexMetadata) // add irrelevant shards noise for completeness (should not happen IRL). sizes.put(new ShardId(index, i), randomLongBetween(0, Integer.MAX_VALUE)); } - ClusterInfo info = new ClusterInfo(Map.of(), Map.of(), Map.of(), sizes, Map.of(), Map.of()); + ClusterInfo info = new ClusterInfo(Map.of(), Map.of(), Map.of(), sizes, Map.of(), Map.of(), Map.of()); return Tuple.tuple(totalSize, info); } } diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ProactiveStorageDeciderServiceTests.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ProactiveStorageDeciderServiceTests.java index 1960b7f2028e7..b252fdf5564db 100644 --- a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ProactiveStorageDeciderServiceTests.java +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ProactiveStorageDeciderServiceTests.java @@ -397,7 +397,7 @@ private ClusterInfo randomClusterInfo(ClusterState state) { for (var id : state.nodes().getDataNodes().keySet()) { diskUsage.put(id, new DiskUsage(id, id, "/test", Long.MAX_VALUE, Long.MAX_VALUE)); } - return new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), Map.of(), Map.of()); + return new ClusterInfo(diskUsage, diskUsage, shardSizes, Map.of(), Map.of(), Map.of(), Map.of()); } private ClusterState.Builder applyCreatedDates( diff --git a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderServiceTests.java b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderServiceTests.java index d159e67482695..2ee94340f6d2c 100644 --- a/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderServiceTests.java +++ b/x-pack/plugin/autoscaling/src/test/java/org/elasticsearch/xpack/autoscaling/storage/ReactiveStorageDeciderServiceTests.java @@ -379,7 +379,7 @@ public void validateSizeOf(ClusterState clusterState, ShardRouting subjectShard, } private ReactiveStorageDeciderService.AllocationState createAllocationState(Map shardSize, ClusterState clusterState) { - ClusterInfo info = new ClusterInfo(Map.of(), Map.of(), shardSize, Map.of(), Map.of(), Map.of()); + ClusterInfo info = new ClusterInfo(Map.of(), Map.of(), shardSize, Map.of(), Map.of(), Map.of(), Map.of()); ReactiveStorageDeciderService.AllocationState allocationState = new ReactiveStorageDeciderService.AllocationState( clusterState, null, @@ -544,7 +544,7 @@ public void testUnmovableSize() { } var diskUsages = Map.of(nodeId, new DiskUsage(nodeId, null, null, ByteSizeUnit.KB.toBytes(100), ByteSizeUnit.KB.toBytes(5))); - ClusterInfo info = new ClusterInfo(diskUsages, diskUsages, shardSize, Map.of(), Map.of(), Map.of()); + ClusterInfo info = new ClusterInfo(diskUsages, diskUsages, shardSize, Map.of(), Map.of(), Map.of(), Map.of()); ReactiveStorageDeciderService.AllocationState allocationState = new ReactiveStorageDeciderService.AllocationState( clusterState, diff --git a/x-pack/plugin/deprecation/src/test/java/org/elasticsearch/xpack/deprecation/TransportNodeDeprecationCheckActionTests.java b/x-pack/plugin/deprecation/src/test/java/org/elasticsearch/xpack/deprecation/TransportNodeDeprecationCheckActionTests.java index a0a37f2bb52d1..40a564088aee6 100644 --- a/x-pack/plugin/deprecation/src/test/java/org/elasticsearch/xpack/deprecation/TransportNodeDeprecationCheckActionTests.java +++ b/x-pack/plugin/deprecation/src/test/java/org/elasticsearch/xpack/deprecation/TransportNodeDeprecationCheckActionTests.java @@ -173,6 +173,7 @@ public void testCheckDiskLowWatermark() { Map.of(), Map.of(), Map.of(), + Map.of(), Map.of() ); DeprecationIssue issue = TransportNodeDeprecationCheckAction.checkDiskLowWatermark( From 99acf4c8bb7c64c56e802577babdea90e816ca20 Mon Sep 17 00:00:00 2001 From: Ignacio Vera Date: Thu, 12 Jun 2025 10:10:29 +0200 Subject: [PATCH 082/102] Revert "Use IndexOrDocValuesQuery in NumberFieldType#termQuery implementations (#128293)" (#129206) This reverts commit de7c91c1d98c4b2b95302655103df2d6eb9cb423. --- docs/changelog/128293.yaml | 5 - .../mapper/extras/ScaledFloatFieldMapper.java | 2 +- .../extras/ScaledFloatFieldTypeTests.java | 3 +- .../index/mapper/NumberFieldMapper.java | 51 ++--- .../index/mapper/NumberFieldTypeTests.java | 216 +++++------------- .../query/MatchPhraseQueryBuilderTests.java | 2 - .../index/query/TermQueryBuilderTests.java | 4 - .../analytics/ttest/TTestAggregatorTests.java | 12 +- .../esql/qa/single_node/PushQueriesIT.java | 5 +- .../AggregateMetricDoubleFieldTypeTests.java | 3 +- 10 files changed, 81 insertions(+), 222 deletions(-) delete mode 100644 docs/changelog/128293.yaml diff --git a/docs/changelog/128293.yaml b/docs/changelog/128293.yaml deleted file mode 100644 index 92181f0b46704..0000000000000 --- a/docs/changelog/128293.yaml +++ /dev/null @@ -1,5 +0,0 @@ -pr: 128293 -summary: Use `IndexOrDocValuesQuery` in `NumberFieldType#termQuery` implementations -area: Search -type: enhancement -issues: [] diff --git a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldMapper.java b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldMapper.java index 9cecba8c7c10b..5bf5295e7adde 100644 --- a/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldMapper.java +++ b/modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldMapper.java @@ -323,7 +323,7 @@ public boolean isSearchable() { public Query termQuery(Object value, SearchExecutionContext context) { failIfNotIndexedNorDocValuesFallback(context); long scaledValue = Math.round(scale(value)); - return NumberFieldMapper.NumberType.LONG.termQuery(name(), scaledValue, isIndexed(), hasDocValues()); + return NumberFieldMapper.NumberType.LONG.termQuery(name(), scaledValue, isIndexed()); } @Override diff --git a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldTypeTests.java b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldTypeTests.java index 31a150a41b227..f2c6f81c3f742 100644 --- a/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldTypeTests.java +++ b/modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldTypeTests.java @@ -30,7 +30,6 @@ import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperBuilderContext; import org.elasticsearch.index.mapper.NumberFieldMapper; -import org.elasticsearch.lucene.document.NumericField; import java.io.IOException; import java.util.Arrays; @@ -48,7 +47,7 @@ public void testTermQuery() { ); double value = (randomDouble() * 2 - 1) * 10000; long scaledValue = Math.round(value * ft.getScalingFactor()); - assertEquals(NumericField.newExactLongQuery("scaled_float", scaledValue), ft.termQuery(value, MOCK_CONTEXT)); + assertEquals(LongPoint.newExactQuery("scaled_float", scaledValue), ft.termQuery(value, MOCK_CONTEXT)); MappedFieldType ft2 = new ScaledFloatFieldMapper.ScaledFloatFieldType("scaled_float", 0.1 + randomDouble() * 100, false); ElasticsearchException e2 = expectThrows(ElasticsearchException.class, () -> ft2.termQuery("42", MOCK_CONTEXT_DISALLOW_EXPENSIVE)); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index e263ebcfeced0..47551252ce7aa 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -46,7 +46,6 @@ import org.elasticsearch.index.fielddata.plain.SortedNumericIndexFieldData; import org.elasticsearch.index.mapper.TimeSeriesParams.MetricType; import org.elasticsearch.index.query.SearchExecutionContext; -import org.elasticsearch.lucene.document.NumericField; import org.elasticsearch.lucene.search.XIndexSortSortedNumericDocValuesRangeQuery; import org.elasticsearch.script.DoubleFieldScript; import org.elasticsearch.script.LongFieldScript; @@ -352,19 +351,13 @@ public Float parse(XContentParser parser, boolean coerce) throws IOException { } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { float v = parseToFloat(value); if (Float.isFinite(HalfFloatPoint.sortableShortToHalfFloat(HalfFloatPoint.halfFloatToSortableShort(v))) == false) { return Queries.newMatchNoDocsQuery("Value [" + value + "] is out of range"); } if (isIndexed) { - if (hasDocValues) { - return new IndexOrDocValuesQuery( - HalfFloatPoint.newExactQuery(field, v), - SortedNumericDocValuesField.newSlowExactQuery(field, HalfFloatPoint.halfFloatToSortableShort(v)) - ); - } return HalfFloatPoint.newExactQuery(field, v); } else { return SortedNumericDocValuesField.newSlowExactQuery(field, HalfFloatPoint.halfFloatToSortableShort(v)); @@ -548,15 +541,13 @@ public Float parse(XContentParser parser, boolean coerce) throws IOException { } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { float v = parseToFloat(value); if (Float.isFinite(v) == false) { return new MatchNoDocsQuery("Value [" + value + "] is out of range"); } - if (isIndexed && hasDocValues) { - return FloatField.newExactQuery(field, v); - } else if (isIndexed) { + if (isIndexed) { return FloatPoint.newExactQuery(field, v); } else { return SortedNumericDocValuesField.newSlowExactQuery(field, NumericUtils.floatToSortableInt(v)); @@ -723,15 +714,13 @@ public FieldValues compile(String fieldName, Script script, ScriptCompil } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { double v = objectToDouble(value); if (Double.isFinite(v) == false) { return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part"); } - if (isIndexed && hasDocValues) { - return DoubleField.newExactQuery(field, v); - } else if (isIndexed) { + if (isIndexed) { return DoublePoint.newExactQuery(field, v); } else { return SortedNumericDocValuesField.newSlowExactQuery(field, NumericUtils.doubleToSortableLong(v)); @@ -885,12 +874,12 @@ public Byte parse(XContentParser parser, boolean coerce) throws IOException { } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { if (isOutOfRange(value)) { return new MatchNoDocsQuery("Value [" + value + "] is out of range"); } - return INTEGER.termQuery(field, value, isIndexed, hasDocValues); + return INTEGER.termQuery(field, value, isIndexed); } @Override @@ -1009,11 +998,11 @@ public Short parse(XContentParser parser, boolean coerce) throws IOException { } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { if (isOutOfRange(value)) { return Queries.newMatchNoDocsQuery("Value [" + value + "] is out of range"); } - return INTEGER.termQuery(field, value, isIndexed, hasDocValues); + return INTEGER.termQuery(field, value, isIndexed); } @Override @@ -1135,7 +1124,7 @@ public Integer parse(XContentParser parser, boolean coerce) throws IOException { } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { if (hasDecimalPart(value)) { return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part"); } @@ -1146,9 +1135,7 @@ public Query termQuery(String field, Object value, boolean isIndexed, boolean ha } int v = parse(value, true); - if (isIndexed && hasDocValues) { - return NumericField.newExactIntQuery(field, v); - } else if (isIndexed) { + if (isIndexed) { return IntPoint.newExactQuery(field, v); } else { return SortedNumericDocValuesField.newSlowExactQuery(field, v); @@ -1321,7 +1308,7 @@ public FieldValues compile(String fieldName, Script script, ScriptCompil } @Override - public Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues) { + public Query termQuery(String field, Object value, boolean isIndexed) { if (hasDecimalPart(value)) { return Queries.newMatchNoDocsQuery("Value [" + value + "] has a decimal part"); } @@ -1330,9 +1317,7 @@ public Query termQuery(String field, Object value, boolean isIndexed, boolean ha } long v = parse(value, true); - if (isIndexed && hasDocValues) { - return NumericField.newExactLongQuery(field, v); - } else if (isIndexed) { + if (isIndexed) { return LongPoint.newExactQuery(field, v); } else { return SortedNumericDocValuesField.newSlowExactQuery(field, v); @@ -1515,7 +1500,7 @@ public final TypeParser parser() { return parser; } - public abstract Query termQuery(String field, Object value, boolean isIndexed, boolean hasDocValues); + public abstract Query termQuery(String field, Object value, boolean isIndexed); public abstract Query termsQuery(String field, Collection values); @@ -1906,11 +1891,11 @@ public NumberFieldType( } public NumberFieldType(String name, NumberType type) { - this(name, type, true, true); + this(name, type, true); } - public NumberFieldType(String name, NumberType type, boolean isIndexed, boolean hasDocValues) { - this(name, type, isIndexed, false, hasDocValues, true, null, Collections.emptyMap(), null, false, null, null, false); + public NumberFieldType(String name, NumberType type, boolean isIndexed) { + this(name, type, isIndexed, false, true, true, null, Collections.emptyMap(), null, false, null, null, false); } @Override @@ -1949,7 +1934,7 @@ public boolean isSearchable() { @Override public Query termQuery(Object value, SearchExecutionContext context) { failIfNotIndexedNorDocValuesFallback(context); - return type.termQuery(name(), value, isIndexed(), hasDocValues()); + return type.termQuery(name(), value, isIndexed()); } @Override diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java index bd06177e17b93..795c0e309b603 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java @@ -12,9 +12,7 @@ import com.carrotsearch.randomizedtesting.generators.RandomPicks; import org.apache.lucene.document.Document; -import org.apache.lucene.document.DoubleField; import org.apache.lucene.document.DoublePoint; -import org.apache.lucene.document.FloatField; import org.apache.lucene.document.FloatPoint; import org.apache.lucene.document.IntPoint; import org.apache.lucene.document.LongPoint; @@ -46,7 +44,6 @@ import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.query.SearchExecutionContextHelper; -import org.elasticsearch.lucene.document.NumericField; import org.elasticsearch.lucene.search.XIndexSortSortedNumericDocValuesRangeQuery; import org.elasticsearch.script.ScriptCompiler; import org.elasticsearch.search.MultiValueMode; @@ -60,6 +57,7 @@ import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -110,117 +108,60 @@ public void testLongTermsQueryWithDecimalPart() { } public void testByteTermQueryWithDecimalPart() { - MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, true, true); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, true, false); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, false, true); + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, randomBoolean()); assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); } public void testShortTermQueryWithDecimalPart() { - MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, true, true); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, false, true); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, true, false); + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, randomBoolean()); assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); } public void testIntegerTermQueryWithDecimalPart() { - MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, true, true); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, true, false); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, false, true); + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, randomBoolean()); assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); } public void testLongTermQueryWithDecimalPart() { - MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, true, true); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, true, false); - assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); - - ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false, true); + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, randomBoolean()); assertTrue(ft.termQuery(42.1, MOCK_CONTEXT) instanceof MatchNoDocsQuery); } - private record TermQueryTestCase(NumberType type, Query[] expectedQueries) {} + private static MappedFieldType unsearchable() { + return new NumberFieldType( + "field", + NumberType.LONG, + false, + false, + false, + true, + null, + Collections.emptyMap(), + null, + false, + null, + null, + false + ); + } public void testTermQuery() { - Query[] expectedIntegerQueries = new Query[] { - NumericField.newExactIntQuery("field", 42), - IntPoint.newExactQuery("field", 42), - SortedNumericDocValuesField.newSlowExactQuery("field", 42) }; - List testCases = List.of( - new TermQueryTestCase(NumberType.BYTE, expectedIntegerQueries), - new TermQueryTestCase(NumberType.SHORT, expectedIntegerQueries), - new TermQueryTestCase(NumberType.INTEGER, expectedIntegerQueries), - new TermQueryTestCase( - NumberType.LONG, - new Query[] { - NumericField.newExactLongQuery("field", 42), - LongPoint.newExactQuery("field", 42), - SortedNumericDocValuesField.newSlowExactQuery("field", 42) } - ), - new TermQueryTestCase( - NumberType.FLOAT, - new Query[] { - FloatField.newExactQuery("field", 42), - FloatPoint.newExactQuery("field", 42), - SortedNumericDocValuesField.newSlowExactQuery("field", NumericUtils.floatToSortableInt(42)) } - ), - new TermQueryTestCase( - NumberType.DOUBLE, - new Query[] { - DoubleField.newExactQuery("field", 42), - DoublePoint.newExactQuery("field", 42), - SortedNumericDocValuesField.newSlowExactQuery("field", NumericUtils.doubleToSortableLong(42)) } - ), - new TermQueryTestCase( - NumberType.HALF_FLOAT, - new Query[] { - new IndexOrDocValuesQuery( - HalfFloatPoint.newExactQuery("field", 42), - SortedNumericDocValuesField.newSlowExactQuery("field", HalfFloatPoint.halfFloatToSortableShort(42)) - ), - HalfFloatPoint.newExactQuery("field", 42), - SortedNumericDocValuesField.newSlowExactQuery("field", HalfFloatPoint.halfFloatToSortableShort(42)) } - ) - ); - - for (TermQueryTestCase testCase : testCases) { - MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", testCase.type()); - assertEquals(testCase.expectedQueries[0], ft.termQuery("42", MOCK_CONTEXT)); - - ft = new NumberFieldMapper.NumberFieldType("field", testCase.type(), true, false); - assertEquals(testCase.expectedQueries[1], ft.termQuery("42", MOCK_CONTEXT)); - - ft = new NumberFieldMapper.NumberFieldType("field", testCase.type(), false, true); - assertEquals(testCase.expectedQueries[2], ft.termQuery("42", MOCK_CONTEXT)); - - MappedFieldType unsearchable = new NumberFieldMapper.NumberFieldType("field", testCase.type(), false, false); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("42", MOCK_CONTEXT)); - assertEquals("Cannot search on field [field] since it is not indexed nor has doc values.", e.getMessage()); - - MappedFieldType ft2 = new NumberFieldMapper.NumberFieldType("field", testCase.type(), false, true); - ElasticsearchException e2 = expectThrows( - ElasticsearchException.class, - () -> ft2.termQuery("42", MOCK_CONTEXT_DISALLOW_EXPENSIVE) - ); - assertEquals( - "Cannot search on field [field] since it is not indexed and 'search.allow_expensive_queries' is set to false.", - e2.getMessage() - ); - } + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG); + assertEquals(LongPoint.newExactQuery("field", 42), ft.termQuery("42", MOCK_CONTEXT)); + + ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false); + assertEquals(SortedNumericDocValuesField.newSlowExactQuery("field", 42), ft.termQuery("42", MOCK_CONTEXT)); + + MappedFieldType unsearchable = unsearchable(); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("42", MOCK_CONTEXT)); + assertEquals("Cannot search on field [field] since it is not indexed nor has doc values.", e.getMessage()); + + MappedFieldType ft2 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false); + ElasticsearchException e2 = expectThrows(ElasticsearchException.class, () -> ft2.termQuery("42", MOCK_CONTEXT_DISALLOW_EXPENSIVE)); + assertEquals( + "Cannot search on field [field] since it is not indexed and 'search.allow_expensive_queries' is set to false.", + e2.getMessage() + ); } private record OutOfRangeTermQueryTestCase(NumberType type, Object value) {} @@ -260,23 +201,12 @@ public void testTermQueryWithOutOfRangeValues() { ); for (OutOfRangeTermQueryTestCase testCase : testCases) { - boolean indexed = randomBoolean(); - boolean hasDocValues = indexed == false || randomBoolean(); - assertTrue(testCase.type.termQuery("field", testCase.value, indexed, hasDocValues) instanceof MatchNoDocsQuery); + assertTrue(testCase.type.termQuery("field", testCase.value, randomBoolean()) instanceof MatchNoDocsQuery); } } public void testRangeQueryWithNegativeBounds() { - testIntegerRangeQueryWithNegativeBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, true, true)); - testIntegerRangeQueryWithNegativeBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, false, true)); - testIntegerRangeQueryWithNegativeBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, true, false)); - - testLongRangeQueryWithNegativeBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, true, true)); - testLongRangeQueryWithNegativeBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, false, true)); - testLongRangeQueryWithNegativeBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, true, false)); - } - - private void testIntegerRangeQueryWithNegativeBounds(MappedFieldType ftInt) { + MappedFieldType ftInt = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, randomBoolean()); assertEquals( ftInt.rangeQuery(-3, -3, true, true, null, null, null, MOCK_CONTEXT), ftInt.rangeQuery(-3.5, -2.5, true, true, null, null, null, MOCK_CONTEXT) @@ -326,9 +256,8 @@ private void testIntegerRangeQueryWithNegativeBounds(MappedFieldType ftInt) { ftInt.rangeQuery(-2, -1, true, true, null, null, null, MOCK_CONTEXT), ftInt.rangeQuery(-2.5, -0.5, false, false, null, null, null, MOCK_CONTEXT) ); - } - private void testLongRangeQueryWithNegativeBounds(MappedFieldType ftLong) { + MappedFieldType ftLong = new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, randomBoolean()); assertEquals( ftLong.rangeQuery(-3, -3, true, true, null, null, null, MOCK_CONTEXT), ftLong.rangeQuery(-3.5, -2.5, true, true, null, null, null, MOCK_CONTEXT) @@ -381,12 +310,7 @@ private void testLongRangeQueryWithNegativeBounds(MappedFieldType ftLong) { } public void testByteRangeQueryWithDecimalParts() { - testByteRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, true, true)); - testByteRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, false, true)); - testByteRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, true, false)); - } - - private void testByteRangeQueryWithDecimalParts(MappedFieldType ft) { + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.BYTE, randomBoolean()); assertEquals( ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_CONTEXT), ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_CONTEXT) @@ -406,12 +330,7 @@ private void testByteRangeQueryWithDecimalParts(MappedFieldType ft) { } public void testShortRangeQueryWithDecimalParts() { - testShortRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, true, true)); - testShortRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, true, false)); - testShortRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, false, true)); - } - - private void testShortRangeQueryWithDecimalParts(MappedFieldType ft) { + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.SHORT, randomBoolean()); assertEquals( ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_CONTEXT), ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_CONTEXT) @@ -431,12 +350,7 @@ private void testShortRangeQueryWithDecimalParts(MappedFieldType ft) { } public void testIntegerRangeQueryWithDecimalParts() { - testIntegerRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, true, true)); - testIntegerRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, false, true)); - testIntegerRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, true, false)); - } - - private void testIntegerRangeQueryWithDecimalParts(MappedFieldType ft) { + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.INTEGER, randomBoolean()); assertEquals( ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_CONTEXT), ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_CONTEXT) @@ -456,12 +370,7 @@ private void testIntegerRangeQueryWithDecimalParts(MappedFieldType ft) { } public void testLongRangeQueryWithDecimalParts() { - testLongRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, true, true)); - testLongRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, false, true)); - testLongRangeQueryWithDecimalParts(new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, true, false)); - } - - private void testLongRangeQueryWithDecimalParts(MappedFieldType ft) { + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.LONG, randomBoolean()); assertEquals( ft.rangeQuery(2, 10, true, true, null, null, null, MOCK_CONTEXT), ft.rangeQuery(1.1, 10, true, true, null, null, null, MOCK_CONTEXT) @@ -481,12 +390,7 @@ private void testLongRangeQueryWithDecimalParts(MappedFieldType ft) { } public void testHalfFloatRangeQueryWithOverflowingBounds() { - testHalfFloatRangeQueryWithOverflowingBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.HALF_FLOAT, true, true)); - testHalfFloatRangeQueryWithOverflowingBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.HALF_FLOAT, false, true)); - testHalfFloatRangeQueryWithOverflowingBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.HALF_FLOAT, true, false)); - } - - private void testHalfFloatRangeQueryWithOverflowingBounds(MappedFieldType ft) { + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.HALF_FLOAT, randomBoolean()); final float min_half_float = -65504; final float max_half_float = 65504; assertEquals( @@ -531,12 +435,8 @@ private void testHalfFloatRangeQueryWithOverflowingBounds(MappedFieldType ft) { } public void testFloatRangeQueryWithOverflowingBounds() { - testFloatRangeQueryWithOverflowingBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.FLOAT, true, true)); - testFloatRangeQueryWithOverflowingBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.FLOAT, false, true)); - testFloatRangeQueryWithOverflowingBounds(new NumberFieldMapper.NumberFieldType("field", NumberType.FLOAT, true, false)); - } + MappedFieldType ft = new NumberFieldMapper.NumberFieldType("field", NumberType.FLOAT, randomBoolean()); - private void testFloatRangeQueryWithOverflowingBounds(MappedFieldType ft) { assertEquals( ft.rangeQuery(-Float.MAX_VALUE, 10.0, true, true, null, null, null, MOCK_CONTEXT), ft.rangeQuery(-1e+300, 10.0, true, true, null, null, null, MOCK_CONTEXT) @@ -586,18 +486,18 @@ public void testRangeQuery() { ); assertEquals(expected, ft.rangeQuery("1", "3", true, true, null, null, null, MOCK_CONTEXT)); - ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false, true); + ft = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false); expected = SortedNumericDocValuesField.newSlowRangeQuery("field", 1, 3); assertEquals(expected, ft.rangeQuery("1", "3", true, true, null, null, null, MOCK_CONTEXT)); - MappedFieldType unsearchable = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false, false); + MappedFieldType unsearchable = unsearchable(); IllegalArgumentException e = expectThrows( IllegalArgumentException.class, () -> unsearchable.rangeQuery("1", "3", true, true, null, null, null, MOCK_CONTEXT) ); assertEquals("Cannot search on field [field] since it is not indexed nor has doc values.", e.getMessage()); - MappedFieldType ft2 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false, true); + MappedFieldType ft2 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.LONG, false); ElasticsearchException e2 = expectThrows( ElasticsearchException.class, () -> ft2.rangeQuery("1", "3", true, true, null, null, null, MOCK_CONTEXT_DISALLOW_EXPENSIVE) @@ -738,19 +638,9 @@ public void testNegativeZero() { NumberType.HALF_FLOAT.rangeQuery("field", null, +0f, true, false, false, MOCK_CONTEXT, isIndexed) ); - final boolean hasDocValues = isIndexed == false || randomBoolean(); // at least one should be true - assertNotEquals( - NumberType.DOUBLE.termQuery("field", -0d, isIndexed, hasDocValues), - NumberType.DOUBLE.termQuery("field", +0d, isIndexed, hasDocValues) - ); - assertNotEquals( - NumberType.FLOAT.termQuery("field", -0f, isIndexed, hasDocValues), - NumberType.FLOAT.termQuery("field", +0f, isIndexed, hasDocValues) - ); - assertNotEquals( - NumberType.HALF_FLOAT.termQuery("field", -0f, isIndexed, hasDocValues), - NumberType.HALF_FLOAT.termQuery("field", +0f, isIndexed, hasDocValues) - ); + assertNotEquals(NumberType.DOUBLE.termQuery("field", -0d, isIndexed), NumberType.DOUBLE.termQuery("field", +0d, isIndexed)); + assertNotEquals(NumberType.FLOAT.termQuery("field", -0f, isIndexed), NumberType.FLOAT.termQuery("field", +0f, isIndexed)); + assertNotEquals(NumberType.HALF_FLOAT.termQuery("field", -0f, isIndexed), NumberType.HALF_FLOAT.termQuery("field", +0f, isIndexed)); } // Make sure we construct the IndexOrDocValuesQuery objects with queries that match diff --git a/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java index d02c2ce29bfa0..4d76f7c522417 100644 --- a/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/MatchPhraseQueryBuilderTests.java @@ -20,7 +20,6 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.ParsingException; import org.elasticsearch.core.Strings; -import org.elasticsearch.lucene.search.XIndexSortSortedNumericDocValuesRangeQuery; import org.elasticsearch.test.AbstractQueryTestCase; import java.io.IOException; @@ -107,7 +106,6 @@ protected void doAssertLuceneQuery(MatchPhraseQueryBuilder queryBuilder, Query q .or(instanceOf(PointRangeQuery.class)) .or(instanceOf(IndexOrDocValuesQuery.class)) .or(instanceOf(MatchNoDocsQuery.class)) - .or(instanceOf(XIndexSortSortedNumericDocValuesRangeQuery.class)) ); } diff --git a/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java index d1428d95788f8..bbac216754eed 100644 --- a/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/TermQueryBuilderTests.java @@ -12,7 +12,6 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.search.AutomatonQuery; -import org.apache.lucene.search.IndexOrDocValuesQuery; import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.PointRangeQuery; import org.apache.lucene.search.Query; @@ -21,7 +20,6 @@ import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.index.mapper.FieldTypeTestCase; import org.elasticsearch.index.mapper.MappedFieldType; -import org.elasticsearch.lucene.search.XIndexSortSortedNumericDocValuesRangeQuery; import org.elasticsearch.xcontent.json.JsonStringEncoder; import org.hamcrest.CoreMatchers; @@ -95,8 +93,6 @@ protected void doAssertLuceneQuery(TermQueryBuilder queryBuilder, Query query, S either(instanceOf(TermQuery.class)).or(instanceOf(PointRangeQuery.class)) .or(instanceOf(MatchNoDocsQuery.class)) .or(instanceOf(AutomatonQuery.class)) - .or(instanceOf(IndexOrDocValuesQuery.class)) - .or(instanceOf(XIndexSortSortedNumericDocValuesRangeQuery.class)) ); MappedFieldType mapper = context.getFieldType(queryBuilder.fieldName()); if (query instanceof TermQuery termQuery) { diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/ttest/TTestAggregatorTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/ttest/TTestAggregatorTests.java index 47ad5d8346b91..dcee4d4b05c21 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/ttest/TTestAggregatorTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/ttest/TTestAggregatorTests.java @@ -582,8 +582,8 @@ public void testHeteroscedastic() throws IOException { public void testFiltered() throws IOException { TTestType tTestType = randomFrom(TTestType.values()); - MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER, false, true); - MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER, true, false); + MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER); + MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER); TTestAggregationBuilder aggregationBuilder = new TTestAggregationBuilder("t_test").a( new MultiValuesSourceFieldConfig.Builder().setFieldName("a").setFilter(QueryBuilders.termQuery("b", 1)).build() ) @@ -638,9 +638,9 @@ public void testFiltered() throws IOException { public void testFilteredAsSubAgg() throws IOException { TTestType tTestType = randomFrom(TTestType.values()); - MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("h", NumberFieldMapper.NumberType.INTEGER, false, true); - MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER, false, true); - MappedFieldType fieldType3 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER, true, false); + MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("h", NumberFieldMapper.NumberType.INTEGER); + MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("a", NumberFieldMapper.NumberType.INTEGER); + MappedFieldType fieldType3 = new NumberFieldMapper.NumberFieldType("b", NumberFieldMapper.NumberType.INTEGER); TTestAggregationBuilder ttestAggregationBuilder = new TTestAggregationBuilder("t_test").a( new MultiValuesSourceFieldConfig.Builder().setFieldName("a").setFilter(QueryBuilders.termQuery("b", 1)).build() ) @@ -711,7 +711,7 @@ public void testFilterByFilterOrScript() throws IOException { boolean fieldInA = randomBoolean(); TTestType tTestType = randomFrom(TTestType.HOMOSCEDASTIC, TTestType.HETEROSCEDASTIC); - MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.INTEGER, false, true); + MappedFieldType fieldType1 = new NumberFieldMapper.NumberFieldType("field", NumberFieldMapper.NumberType.INTEGER); MappedFieldType fieldType2 = new NumberFieldMapper.NumberFieldType("term", NumberFieldMapper.NumberType.INTEGER); boolean filterTermOne = randomBoolean(); diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java index b7e94b8590fe2..5a007dc2c9592 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/PushQueriesIT.java @@ -152,10 +152,7 @@ public void testEqualityAndOther() throws IOException { * single_value_match is here because there are extra documents hiding in the index * that don't have the `foo` field. */ - List.of( - "#foo:[1 TO 1] #single_value_match(foo) #FieldExistsQuery [field=_primary_term]", - "#foo:[1 TO 1] #FieldExistsQuery [field=_primary_term]" - ); + List.of("#foo:[1 TO 1] #single_value_match(foo)", "foo:[1 TO 1]"); default -> throw new UnsupportedOperationException("unknown type [" + type + "]"); }; boolean filterInCompute = switch (type) { diff --git a/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldTypeTests.java b/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldTypeTests.java index ee5609d8f5735..55ecfc13b1f3e 100644 --- a/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldTypeTests.java +++ b/x-pack/plugin/mapper-aggregate-metric/src/test/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldTypeTests.java @@ -6,7 +6,6 @@ */ package org.elasticsearch.xpack.aggregatemetric.mapper; -import org.apache.lucene.document.DoubleField; import org.apache.lucene.document.DoublePoint; import org.apache.lucene.document.NumericDocValuesField; import org.apache.lucene.index.DirectoryReader; @@ -66,7 +65,7 @@ protected AggregateMetricDoubleFieldType createDefaultFieldType(String name, Map public void testTermQuery() { final MappedFieldType fieldType = createDefaultFieldType("foo", Collections.emptyMap(), Metric.max); Query query = fieldType.termQuery(55.2, MOCK_CONTEXT); - assertThat(query, equalTo(DoubleField.newRangeQuery("foo.max", 55.2, 55.2))); + assertThat(query, equalTo(DoublePoint.newRangeQuery("foo.max", 55.2, 55.2))); } public void testTermsQuery() { From a6106987b94e35f1b032fa1a5ec86bb08bdd0d65 Mon Sep 17 00:00:00 2001 From: Richard Dennehy Date: Thu, 12 Jun 2025 10:03:32 +0100 Subject: [PATCH 083/102] Delegated authorization using Microsoft Graph (SDK) (#128396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Delegated authorization using Microsoft Graph (SDK) --------- Co-authored-by: elasticsearchmachine Co-authored-by: Johannes Freden Jansson Co-authored-by: Johannes Fredén <109296772+jfreden@users.noreply.github.com> --- .../patches/{hdfs => }/MethodReplacement.java | 4 +- .../azurecore/AzureCoreClassPatcher.java | 61 +++ .../patches/azurecore/ImplUtilsPatcher.java | 34 ++ .../patches/hdfs/ShellPatcher.java | 1 + .../hdfs/ShutdownHookManagerPatcher.java | 1 + distribution/docker/build.gradle | 1 + docs/changelog/128396.yaml | 5 + gradle/verification-metadata.xml | 115 ++++ .../src/main/java/module-info.java | 19 - .../microsoft/MicrosoftGraphAuthzRealm.java | 50 -- .../MicrosoftGraphAuthzRealmSettings.java | 24 - settings.gradle | 3 +- .../extras}/build.gradle | 13 - x-pack/extras/plugins/build.gradle | 27 + .../microsoft-graph-authz/build.gradle | 194 +++++++ .../kiota-merged/build.gradle | 75 +++ .../kiota-merged/licenses/kiota-LICENSE.txt | 21 + .../kiota-merged/licenses/kiota-NOTICE.txt | 0 .../licenses/azure-LICENSE.txt | 21 + .../licenses/azure-NOTICE.txt | 0 .../licenses/gson-LICENSE.txt | 202 +++++++ .../licenses/gson-NOTICE.txt | 0 .../licenses/jackson-LICENSE.txt | 8 + .../licenses/jackson-NOTICE.txt | 20 + .../licenses/jna-LICENSE.txt | 177 ++++++ .../licenses/jna-NOTICE.txt | 1 + .../licenses/kotlin-LICENSE.txt | 202 +++++++ .../licenses/kotlin-NOTICE.txt | 8 + .../licenses/microsoft-graph-LICENSE.txt | 21 + .../licenses/microsoft-graph-NOTICE.txt | 0 .../licenses/msal4j-LICENSE.txt | 21 + .../licenses/msal4j-NOTICE.txt | 0 .../licenses/okhttp-LICENSE.txt | 202 +++++++ .../licenses/okhttp-NOTICE.txt | 0 .../licenses/okio-LICENSE.txt | 202 +++++++ .../licenses/okio-NOTICE.txt | 0 .../licenses/opentelemetry-LICENSE.txt | 201 +++++++ .../licenses/opentelemetry-NOTICE.txt | 0 .../licenses/reactive-streams-LICENSE.txt | 7 + .../licenses/reactive-streams-NOTICE.txt | 0 .../licenses/reactor-core-LICENSE.txt | 202 +++++++ .../licenses/reactor-core-NOTICE.txt | 0 .../licenses/std-uritemplate-LICENSE.txt | 201 +++++++ .../licenses/std-uritemplate-NOTICE.txt | 0 .../src/main/java/module-info.java | 28 + .../microsoft/MicrosoftGraphAuthzPlugin.java | 13 +- .../microsoft/MicrosoftGraphAuthzRealm.java | 235 ++++++++ .../MicrosoftGraphAuthzRealmSettings.java | 71 +++ .../plugin-metadata/entitlement-policy.yaml | 5 + ...arch.xpack.core.security.SecurityExtension | 0 .../MicrosoftGraphAuthzRealmTests.java | 508 ++++++++++++++++++ .../security/support/CancellableRunnable.java | 76 +++ .../support/CancellableRunnableTests.java} | 15 +- x-pack/plugin/security/build.gradle | 6 +- .../security/licenses/nimbus-LICENSE.txt | 202 +++++++ .../security/licenses/nimbus-NOTICE.txt | 0 .../microsoft-graph-authz-tests/build.gradle | 8 +- .../MicrosoftGraphAuthzPluginIT.java | 145 ++++- .../microsoft/MicrosoftGraphHttpFixture.java | 296 ++++++++++ .../javaRestTest/resources/server/cert.key | 28 + .../javaRestTest/resources/server/cert.pem | 20 + .../xpack/security/authc/ldap/LdapRealm.java | 70 +-- 62 files changed, 3865 insertions(+), 205 deletions(-) rename build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/{hdfs => }/MethodReplacement.java (88%) create mode 100644 build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java create mode 100644 build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java create mode 100644 docs/changelog/128396.yaml delete mode 100644 plugins/microsoft-graph-authz/src/main/java/module-info.java delete mode 100644 plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java delete mode 100644 plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java rename {plugins/microsoft-graph-authz => x-pack/extras}/build.gradle (55%) create mode 100644 x-pack/extras/plugins/build.gradle create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/build.gradle create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/build.gradle create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java (60%) create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml rename {plugins => x-pack/extras/plugins}/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension (100%) create mode 100644 x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java rename x-pack/plugin/{security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java => core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java} (84%) create mode 100644 x-pack/plugin/security/licenses/nimbus-LICENSE.txt create mode 100644 x-pack/plugin/security/licenses/nimbus-NOTICE.txt create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key create mode 100644 x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/MethodReplacement.java similarity index 88% rename from build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java rename to build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/MethodReplacement.java index 7bc6a6c0d530f..20bc03dde2ae3 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/MethodReplacement.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/MethodReplacement.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.gradle.internal.dependencies.patches.hdfs; +package org.elasticsearch.gradle.internal.dependencies.patches; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -16,7 +16,7 @@ public class MethodReplacement extends MethodVisitor { private final MethodVisitor delegate; private final Runnable bodyWriter; - MethodReplacement(MethodVisitor delegate, Runnable bodyWriter) { + public MethodReplacement(MethodVisitor delegate, Runnable bodyWriter) { super(Opcodes.ASM9); this.delegate = delegate; this.bodyWriter = bodyWriter; diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java new file mode 100644 index 0000000000000..43b7381fbbfc1 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/AzureCoreClassPatcher.java @@ -0,0 +1,61 @@ +/* + * 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.gradle.internal.dependencies.patches.azurecore; + +import org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo; +import org.elasticsearch.gradle.internal.dependencies.patches.Utils; +import org.gradle.api.artifacts.transform.CacheableTransform; +import org.gradle.api.artifacts.transform.InputArtifact; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformOutputs; +import org.gradle.api.artifacts.transform.TransformParameters; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Classpath; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; +import java.util.regex.Pattern; + +import static org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo.classPatcher; + +@CacheableTransform +public abstract class AzureCoreClassPatcher implements TransformAction { + + private static final String JAR_FILE_TO_PATCH = "azure-core-[\\d.]*\\.jar"; + + private static final List CLASS_PATCHERS = List.of( + classPatcher( + "com/azure/core/implementation/ImplUtils.class", + "7beda5bdff5ea460cfc08721a188cf07d16e0c987dae45401fca7abf4e6e6c0e", + ImplUtilsPatcher::new + ) + ); + + @Classpath + @InputArtifact + public abstract Provider getInputArtifact(); + + @Override + public void transform(@NotNull TransformOutputs outputs) { + File inputFile = getInputArtifact().get().getAsFile(); + + if (Pattern.matches(JAR_FILE_TO_PATCH, inputFile.getName())) { + System.out.println("Patching " + inputFile.getName()); + File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar")); + Utils.patchJar(inputFile, outputFile, CLASS_PATCHERS, true); + } else { + System.out.println("Skipping " + inputFile.getName()); + outputs.file(getInputArtifact()); + } + } + +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java new file mode 100644 index 0000000000000..a76f8cb9468ff --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/azurecore/ImplUtilsPatcher.java @@ -0,0 +1,34 @@ +/* + * 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.gradle.internal.dependencies.patches.azurecore; + +import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +class ImplUtilsPatcher extends ClassVisitor { + ImplUtilsPatcher(ClassVisitor classVisitor) { + super(Opcodes.ASM9, classVisitor); + } + + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + // `addShutdownHook` invokes `java.lang.Runtime.addShutdownHook`, which is forbidden (i.e. it will throw an Entitlements error). + // We replace the method body here with `return null`. + if (name.equals("addShutdownHookSafely")) { + return new MethodReplacement(mv, () -> { + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitInsn(Opcodes.ARETURN); + }); + } + return mv; + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java index ab63249f5c8e8..25b802aa95228 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShellPatcher.java @@ -9,6 +9,7 @@ package org.elasticsearch.gradle.internal.dependencies.patches.hdfs; +import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java index 4efe48a3bf72d..dbac740b208a7 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/ShutdownHookManagerPatcher.java @@ -9,6 +9,7 @@ package org.elasticsearch.gradle.internal.dependencies.patches.hdfs; +import org.elasticsearch.gradle.internal.dependencies.patches.MethodReplacement; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; diff --git a/distribution/docker/build.gradle b/distribution/docker/build.gradle index e993c46101458..e3f87117ea029 100644 --- a/distribution/docker/build.gradle +++ b/distribution/docker/build.gradle @@ -117,6 +117,7 @@ dependencies { log4jConfig project(path: ":distribution", configuration: 'log4jConfig') tini "krallin:tini:0.19.0:${tiniArch}" allPlugins project(path: ':plugins', configuration: 'allPlugins') + allPlugins project(path: ':x-pack:extras:plugins', configuration: 'allPlugins') filebeat_aarch64 "beats:filebeat:${VersionProperties.elasticsearch}:linux-arm64@tar.gz" filebeat_x86_64 "beats:filebeat:${VersionProperties.elasticsearch}:linux-x86_64@tar.gz" filebeat_fips_aarch64 "beats:filebeat-fips:${VersionProperties.elasticsearch}:linux-arm64@tar.gz" diff --git a/docs/changelog/128396.yaml b/docs/changelog/128396.yaml new file mode 100644 index 0000000000000..6e19a83d156e5 --- /dev/null +++ b/docs/changelog/128396.yaml @@ -0,0 +1,5 @@ +pr: 128396 +summary: Delegated authorization using Microsoft Graph (SDK) +area: Authorization +type: feature +issues: [] diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index cf520c208cfd8..3605e71501873 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -96,21 +96,41 @@ + + + + + + + + + + + + + + + + + + + + @@ -136,6 +156,11 @@ + + + + + @@ -983,11 +1008,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1078,6 +1153,11 @@ + + + + + @@ -1098,6 +1178,16 @@ + + + + + + + + + + @@ -1448,6 +1538,11 @@ + + + + + @@ -1583,6 +1678,11 @@ + + + + + @@ -1593,6 +1693,11 @@ + + + + + @@ -1613,6 +1718,11 @@ + + + + + @@ -4006,6 +4116,11 @@ + + + + + diff --git a/plugins/microsoft-graph-authz/src/main/java/module-info.java b/plugins/microsoft-graph-authz/src/main/java/module-info.java deleted file mode 100644 index 9a149da12f6e9..0000000000000 --- a/plugins/microsoft-graph-authz/src/main/java/module-info.java +++ /dev/null @@ -1,19 +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", 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". - */ - -import org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin; - -module org.elasticsearch.plugin.security.authz { - requires org.elasticsearch.base; - requires org.elasticsearch.server; - requires org.elasticsearch.xcore; - requires org.elasticsearch.logging; - - provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; -} diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java deleted file mode 100644 index 4408216545fb6..0000000000000 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java +++ /dev/null @@ -1,50 +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", 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.xpack.security.authz.microsoft; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.logging.LogManager; -import org.elasticsearch.logging.Logger; -import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; -import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; -import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.authc.RealmConfig; -import org.elasticsearch.xpack.core.security.user.User; - -public class MicrosoftGraphAuthzRealm extends Realm { - - private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); - - public MicrosoftGraphAuthzRealm(RealmConfig config) { - super(config); - } - - @Override - public boolean supports(AuthenticationToken token) { - return false; - } - - @Override - public AuthenticationToken token(ThreadContext context) { - return null; - } - - @Override - public void authenticate(AuthenticationToken token, ActionListener> listener) { - listener.onResponse(AuthenticationResult.notHandled()); - } - - @Override - public void lookupUser(String username, ActionListener listener) { - logger.info("Microsoft Graph Authz not yet implemented, returning empty roles for [{}]", username); - listener.onResponse(new User(username)); - } -} diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java deleted file mode 100644 index 46965968b79a6..0000000000000 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java +++ /dev/null @@ -1,24 +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", 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.xpack.security.authz.microsoft; - -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.xpack.core.security.authc.RealmSettings; - -import java.util.ArrayList; -import java.util.List; - -public class MicrosoftGraphAuthzRealmSettings { - public static final String REALM_TYPE = "microsoft_graph"; - - public static List> getSettings() { - return new ArrayList<>(RealmSettings.getStandardSettings(REALM_TYPE)); - } -} diff --git a/settings.gradle b/settings.gradle index 6bca35f40bb26..f3463efc5af38 100644 --- a/settings.gradle +++ b/settings.gradle @@ -155,6 +155,7 @@ addSubProjects('', new File(rootProject.projectDir, 'qa')) addSubProjects('test', new File(rootProject.projectDir, 'test/external-modules')) addSubProjects('', new File(rootProject.projectDir, 'x-pack')) addSubProjects('', new File(rootProject.projectDir, 'x-pack/libs')) +addSubProjects('', new File(rootProject.projectDir, 'x-pack/extras/plugins')) include projects.toArray(new String[0]) @@ -172,4 +173,4 @@ if (extraProjects.exists()) { } } -include 'qa:vector' \ No newline at end of file +include 'qa:vector' diff --git a/plugins/microsoft-graph-authz/build.gradle b/x-pack/extras/build.gradle similarity index 55% rename from plugins/microsoft-graph-authz/build.gradle rename to x-pack/extras/build.gradle index f2b30c57c3755..ad21076a9ca93 100644 --- a/plugins/microsoft-graph-authz/build.gradle +++ b/x-pack/extras/build.gradle @@ -6,16 +6,3 @@ * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ - -apply plugin: "elasticsearch.internal-java-rest-test" - -esplugin { - name = "microsoft-graph-authz" - description = "Microsoft Graph Delegated Authorization Realm Plugin" - classname = "org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin" - extendedPlugins = ["x-pack-security"] -} - -dependencies { - compileOnly project(":x-pack:plugin:core") -} diff --git a/x-pack/extras/plugins/build.gradle b/x-pack/extras/plugins/build.gradle new file mode 100644 index 0000000000000..614420a18f03d --- /dev/null +++ b/x-pack/extras/plugins/build.gradle @@ -0,0 +1,27 @@ +/* + * 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". + */ + +configurations { + allPlugins +} + +// only configure immediate children of plugins dir +configure(subprojects.findAll { it.parent.path == project.path }) { + group = 'org.elasticsearch.plugin' + apply plugin: 'elasticsearch.internal-es-plugin' + + esplugin { + // for local ES plugins, the name of the plugin is the same as the directory + name = project.name + licenseFile = layout.settingsDirectory.file('licenses/ELASTIC-LICENSE-2.0.txt').asFile + noticeFile = layout.settingsDirectory.file('NOTICE.txt').asFile + } + + parent.artifacts.add('allPlugins', tasks.named('bundlePlugin')) +} diff --git a/x-pack/extras/plugins/microsoft-graph-authz/build.gradle b/x-pack/extras/plugins/microsoft-graph-authz/build.gradle new file mode 100644 index 0000000000000..5d46638921450 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/build.gradle @@ -0,0 +1,194 @@ +/* + * 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". + */ + +plugins { + id "elasticsearch.internal-java-rest-test" +} + +esplugin { + name = "microsoft-graph-authz" + description = "Microsoft Graph Delegated Authorization Realm Plugin" + classname = "org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin" + extendedPlugins = ["x-pack-security"] +} + +def patched = Attribute.of('patched', Boolean) + +configurations { + compileClasspath { + attributes { + attribute(patched, true) + } + } + runtimeClasspath { + attributes { + attribute(patched, true) + } + } + testCompileClasspath { + attributes { + attribute(patched, true) + } + } + testRuntimeClasspath { + attributes { + attribute(patched, true) + } + } +} + +dependencies { + compileOnly project(":x-pack:plugin:core") + + implementation "com.microsoft.graph:microsoft-graph:6.36.0" + implementation "com.microsoft.graph:microsoft-graph-core:3.6.1" + implementation project(path: "kiota-merged", configuration: 'shadow') + implementation "com.azure:azure-identity:1.15.4" + implementation "com.azure:azure-core:1.55.3" + implementation "com.azure:azure-json:1.5.0" + implementation "com.azure:azure-xml:1.2.0" + implementation "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" + implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson}" + implementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${versions.jackson}" + implementation "org.reactivestreams:reactive-streams:1.0.4" + implementation "io.projectreactor:reactor-core:3.7.5" + compileOnly "org.slf4j:slf4j-api:${versions.slf4j}" + runtimeOnly "com.microsoft.azure:msal4j:1.19.1" + runtimeOnly "com.microsoft.azure:msal4j-persistence-extension:1.3.0" + runtimeOnly "net.java.dev.jna:jna:${versions.jna}" + runtimeOnly "net.java.dev.jna:jna-platform:${versions.jna}" + runtimeOnly "io.opentelemetry:opentelemetry-api:1.50.0" + runtimeOnly "io.opentelemetry:opentelemetry-context:1.50.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.20" + implementation "com.squareup.okhttp3:okhttp:4.11.0" + runtimeOnly "com.squareup.okio:okio:3.4.0" + runtimeOnly "com.squareup.okio:okio-jvm:3.4.0" + runtimeOnly "io.github.std-uritemplate:std-uritemplate:2.0.0" + implementation "com.azure:azure-core-http-okhttp:1.12.10" + implementation "com.google.code.gson:gson:2.10" + + testRuntimeOnly "net.minidev:json-smart:2.5.2" + testRuntimeOnly "com.nimbusds:oauth2-oidc-sdk:11.22.2" + testRuntimeOnly "com.nimbusds:content-type:2.3" + testImplementation testArtifact(project(":x-pack:plugin:core")) + + attributesSchema { + attribute(patched) + } + artifactTypes.getByName("jar") { + attributes.attribute(patched, false) + } + registerTransform(org.elasticsearch.gradle.internal.dependencies.patches.azurecore.AzureCoreClassPatcher) { + from.attribute(patched, false) + to.attribute(patched, true) + } +} + +tasks.named("javadoc").configure { enabled = false } + +tasks.named("dependencyLicenses").configure { + mapping from: "microsoft-graph-core", to: "microsoft-graph" + mapping from: /azure-.*/, to: "azure" + mapping from: /jackson.*/, to: "jackson" + mapping from: /kotlin.*/, to: "kotlin" + mapping from: /msal4j.*/, to: "msal4j" + mapping from: /jna.*/, to: "jna" + mapping from: /opentelemetry.*/, to: "opentelemetry" + mapping from: /okio.*/, to: "okio" +} + +tasks.named("thirdPartyAudit").configure { + ignoreViolations( + 'reactor.core.publisher.CallSiteSupplierFactory$SharedSecretsCallSiteSupplierFactory', + 'reactor.core.publisher.CallSiteSupplierFactory$SharedSecretsCallSiteSupplierFactory$TracingException' + ) + + ignoreMissingClasses( + 'android.net.http.X509TrustManagerExtensions', + 'android.net.ssl.SSLSockets', + 'android.os.Build$VERSION', + 'android.security.NetworkSecurityPolicy', + 'android.util.Log', + 'com.auth0.jwk.Jwk', + 'com.auth0.jwk.JwkProvider', + 'com.nimbusds.common.contenttype.ContentType', + 'com.nimbusds.jose.JWSAlgorithm', + 'com.nimbusds.jose.JWSHeader$Builder', + 'com.nimbusds.jose.util.Base64URL', + 'com.nimbusds.jose.util.StandardCharset', + 'com.nimbusds.jwt.JWT', + 'com.nimbusds.jwt.JWTClaimsSet', + 'com.nimbusds.jwt.JWTClaimsSet$Builder', + 'com.nimbusds.jwt.JWTParser', + 'com.nimbusds.jwt.SignedJWT', + 'com.nimbusds.oauth2.sdk.AuthorizationGrant', + 'com.nimbusds.oauth2.sdk.GrantType', + 'com.nimbusds.oauth2.sdk.ParseException', + 'com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant', + 'com.nimbusds.oauth2.sdk.SAML2BearerGrant', + 'com.nimbusds.oauth2.sdk.auth.ClientAuthentication', + 'com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod', + 'com.nimbusds.oauth2.sdk.auth.JWTAuthenticationClaimsSet', + 'com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT', + 'com.nimbusds.oauth2.sdk.auth.Secret', + 'com.nimbusds.oauth2.sdk.http.HTTPRequest', + 'com.nimbusds.oauth2.sdk.http.HTTPRequest$Method', + 'com.nimbusds.oauth2.sdk.http.HTTPResponse', + 'com.nimbusds.oauth2.sdk.id.ClientID', + 'com.nimbusds.oauth2.sdk.token.AccessToken', + 'com.nimbusds.oauth2.sdk.token.RefreshToken', + 'com.nimbusds.oauth2.sdk.util.JSONObjectUtils', + 'com.nimbusds.oauth2.sdk.util.URLUtils', + 'com.nimbusds.openid.connect.sdk.OIDCTokenResponse', + 'com.nimbusds.openid.connect.sdk.token.OIDCTokens', + 'io.jsonwebtoken.Claims', + 'io.jsonwebtoken.JweHeader', + 'io.jsonwebtoken.Jws', + 'io.jsonwebtoken.JwsHeader', + 'io.jsonwebtoken.JwtParser', + 'io.jsonwebtoken.JwtParserBuilder', + 'io.jsonwebtoken.Jwts', + 'io.jsonwebtoken.LocatorAdapter', + 'io.micrometer.context.ContextAccessor', + 'io.micrometer.context.ContextRegistry', + 'io.micrometer.context.ContextSnapshot', + 'io.micrometer.context.ContextSnapshot$Scope', + 'io.micrometer.context.ContextSnapshotFactory', + 'io.micrometer.context.ContextSnapshotFactory$Builder', + 'io.micrometer.context.ThreadLocalAccessor', + 'io.micrometer.core.instrument.Clock', + 'io.micrometer.core.instrument.Counter', + 'io.micrometer.core.instrument.Counter$Builder', + 'io.micrometer.core.instrument.DistributionSummary', + 'io.micrometer.core.instrument.DistributionSummary$Builder', + 'io.micrometer.core.instrument.Meter', + 'io.micrometer.core.instrument.MeterRegistry', + 'io.micrometer.core.instrument.Metrics', + 'io.micrometer.core.instrument.Tag', + 'io.micrometer.core.instrument.Tags', + 'io.micrometer.core.instrument.Timer', + 'io.micrometer.core.instrument.Timer$Builder', + 'io.micrometer.core.instrument.Timer$Sample', + 'io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics', + 'io.micrometer.core.instrument.composite.CompositeMeterRegistry', + 'io.micrometer.core.instrument.search.Search', + 'kotlin.io.path.PathsKt', + 'net.minidev.json.JSONObject', + 'org.bouncycastle.jsse.BCSSLParameters', + 'org.bouncycastle.jsse.BCSSLSocket', + 'org.conscrypt.Conscrypt', + 'org.conscrypt.Conscrypt$Version', + 'org.conscrypt.ConscryptHostnameVerifier', + 'org.openjsse.javax.net.ssl.SSLParameters', + 'org.openjsse.javax.net.ssl.SSLSocket', + 'reactor.blockhound.BlockHound$Builder', + 'reactor.blockhound.integration.BlockHoundIntegration' + ) +} diff --git a/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/build.gradle b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/build.gradle new file mode 100644 index 0000000000000..e714522ab13b8 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/build.gradle @@ -0,0 +1,75 @@ +/* + * 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". + */ + +apply plugin: 'elasticsearch.build' +apply plugin: 'com.gradleup.shadow' + +// because each of these declares a module `com.microsoft.kiota`, the plugin will crash at runtime; we work around this by building a shadow "uber jar" +// so that there is only one `com.microsoft.kiota` module +dependencies { + implementation "com.microsoft.kiota:microsoft-kiota-abstractions:1.8.4" + implementation "com.microsoft.kiota:microsoft-kiota-authentication-azure:1.8.4" + implementation "com.microsoft.kiota:microsoft-kiota-http-okHttp:1.8.4" + implementation "com.microsoft.kiota:microsoft-kiota-serialization-json:1.8.4" + implementation "com.microsoft.kiota:microsoft-kiota-serialization-text:1.8.4" + implementation "com.microsoft.kiota:microsoft-kiota-serialization-form:1.8.4" + implementation "com.microsoft.kiota:microsoft-kiota-serialization-multipart:1.8.4" +} + +tasks.named('shadowJar').configure { + manifest { + attributes 'Automatic-Module-Name': 'com.microsoft.kiota' + } +} + +tasks.named("dependencyLicenses").configure { + mapping from: /microsoft-kiota.*/, to: "kiota" +} + +tasks.named("thirdPartyAudit").configure { + ignoreMissingClasses( + 'com.azure.core.credential.AccessToken', + 'com.azure.core.credential.TokenCredential', + 'com.azure.core.credential.TokenRequestContext', + 'com.google.gson.JsonArray', + 'com.google.gson.JsonElement', + 'com.google.gson.JsonObject', + 'com.google.gson.JsonParser', + 'com.google.gson.JsonPrimitive', + 'com.google.gson.stream.JsonWriter', + 'io.github.stduritemplate.StdUriTemplate', + 'io.opentelemetry.api.GlobalOpenTelemetry', + 'io.opentelemetry.api.common.AttributeKey', + 'io.opentelemetry.api.trace.Span', + 'io.opentelemetry.api.trace.SpanBuilder', + 'io.opentelemetry.api.trace.StatusCode', + 'io.opentelemetry.api.trace.Tracer', + 'io.opentelemetry.context.Context', + 'io.opentelemetry.context.Scope', + 'kotlin.Pair', + 'okhttp3.Call', + 'okhttp3.Call$Factory', + 'okhttp3.Headers', + 'okhttp3.HttpUrl', + 'okhttp3.HttpUrl$Builder', + 'okhttp3.Interceptor', + 'okhttp3.Interceptor$Chain', + 'okhttp3.MediaType', + 'okhttp3.OkHttpClient$Builder', + 'okhttp3.Protocol', + 'okhttp3.Request', + 'okhttp3.Request$Builder', + 'okhttp3.RequestBody', + 'okhttp3.Response', + 'okhttp3.Response$Builder', + 'okhttp3.ResponseBody', + 'okio.BufferedSink', + 'okio.Okio' + ) +} diff --git a/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE.txt new file mode 100644 index 0000000000000..9e841e7a26e4e --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-LICENSE.txt @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/kiota-merged/licenses/kiota-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt new file mode 100644 index 0000000000000..3423e9584d153 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/azure-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/gson-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE.txt new file mode 100644 index 0000000000000..f5f45d26a49d6 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-LICENSE.txt @@ -0,0 +1,8 @@ +This copy of Jackson JSON processor streaming parser/generator is licensed under the +Apache (Software) License, version 2.0 ("the License"). +See the License for details about distribution rights, and the +specific rights regarding derivate works. + +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE.txt new file mode 100644 index 0000000000000..4c976b7b4cc58 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jackson-NOTICE.txt @@ -0,0 +1,20 @@ +# Jackson JSON processor + +Jackson is a high-performance, Free/Open Source JSON processing library. +It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has +been in development since 2007. +It is currently developed by a community of developers, as well as supported +commercially by FasterXML.com. + +## Licensing + +Jackson core and extension components may licensed under different licenses. +To find the details that apply to this artifact see the accompanying LICENSE file. +For more information, including possible other licensing options, contact +FasterXML.com (http://fasterxml.com). + +## Credits + +A list of contributors may be found from CREDITS file, which is included +in some artifacts (usually source distributions); but is always available +from the source code management (SCM) system project uses. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt new file mode 100644 index 0000000000000..f433b1a53f5b8 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-LICENSE.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt new file mode 100644 index 0000000000000..8d1c8b69c3fce --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/jna-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt new file mode 100644 index 0000000000000..80dbbefa483f6 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/kotlin-NOTICE.txt @@ -0,0 +1,8 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Kotlin Compiler distribution. == + ========================================================================= + + Kotlin Compiler + Copyright 2010-2024 JetBrains s.r.o and respective authors and developers diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt new file mode 100644 index 0000000000000..fce1f3f63ef42 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Microsoft Graph + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/microsoft-graph-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt new file mode 100644 index 0000000000000..21071075c2459 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-LICENSE.txt @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/msal4j-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okhttp-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/okio-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt new file mode 100644 index 0000000000000..261eeb9e9f8b2 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/opentelemetry-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt new file mode 100644 index 0000000000000..1e141c13ddba2 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-LICENSE.txt @@ -0,0 +1,7 @@ +MIT No Attribution + +Copyright 2014 Reactive Streams + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactive-streams-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt new file mode 100644 index 0000000000000..d5dd862b1759b --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed 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 + + https://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. + diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/reactor-core-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt new file mode 100644 index 0000000000000..c5127d773bd88 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 std-uritemplate + + Licensed 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. diff --git a/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt b/x-pack/extras/plugins/microsoft-graph-authz/licenses/std-uritemplate-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java new file mode 100644 index 0000000000000..e631b44f1feb4 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/module-info.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +import org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzPlugin; + +module org.elasticsearch.plugin.security.authz { + requires org.elasticsearch.base; + requires org.elasticsearch.server; + requires org.elasticsearch.xcore; + requires org.elasticsearch.logging; + requires org.apache.httpcomponents.httpclient; + requires org.apache.httpcomponents.httpcore; + requires com.microsoft.kiota; + requires com.microsoft.graph; + requires com.azure.identity; + requires com.microsoft.graph.core; + requires kotlin.stdlib; + requires com.google.gson; + requires okhttp3; + requires com.azure.core.http.okhttp; + requires org.apache.logging.log4j; + + provides org.elasticsearch.xpack.core.security.SecurityExtension with MicrosoftGraphAuthzPlugin; +} diff --git a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java similarity index 60% rename from plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java index 71d06d403356e..8bc99c1772380 100644 --- a/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPlugin.java @@ -1,10 +1,8 @@ /* * 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". + * 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.security.authz.microsoft; @@ -20,7 +18,10 @@ public class MicrosoftGraphAuthzPlugin extends Plugin implements SecurityExtension { @Override public Map getRealms(SecurityComponents components) { - return Map.of(MicrosoftGraphAuthzRealmSettings.REALM_TYPE, MicrosoftGraphAuthzRealm::new); + return Map.of( + MicrosoftGraphAuthzRealmSettings.REALM_TYPE, + config -> new MicrosoftGraphAuthzRealm(components.roleMapper(), config, components.threadPool()) + ); } @Override diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java new file mode 100644 index 0000000000000..04f2b177cf018 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealm.java @@ -0,0 +1,235 @@ +/* + * 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.security.authz.microsoft; + +import okhttp3.OkHttpClient; + +import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.microsoft.graph.core.requests.BaseGraphRequestAdapter; +import com.microsoft.graph.core.tasks.PageIterator; +import com.microsoft.graph.models.Group; +import com.microsoft.graph.models.GroupCollectionResponse; +import com.microsoft.graph.serviceclient.GraphServiceClient; +import com.microsoft.kiota.authentication.AzureIdentityAuthenticationProvider; +import com.microsoft.kiota.http.middleware.RetryHandler; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.SettingsException; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.core.Tuple; +import org.elasticsearch.license.License; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.LicensedFeature; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.XPackPlugin; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.Realm; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.RealmSettings; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.support.CancellableRunnable; +import org.elasticsearch.xpack.core.security.user.User; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class MicrosoftGraphAuthzRealm extends Realm { + + private static final Logger logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); + private static final int PAGE_SIZE = 999; + + private static final boolean DISABLE_INSTANCE_DISCOVERY = System.getProperty( + "tests.azure.credentials.disable_instance_discovery", + "false" + ).equals("true"); + + static final LicensedFeature.Momentary MICROSOFT_GRAPH_FEATURE = LicensedFeature.momentary( + "security-realms", + "microsoft_graph", + License.OperationMode.PLATINUM + ); + + private final RealmConfig config; + private final UserRoleMapper roleMapper; + private final GraphServiceClient client; + private final XPackLicenseState licenseState; + private final ThreadPool threadPool; + private final TimeValue executionTimeout; + + public MicrosoftGraphAuthzRealm(UserRoleMapper roleMapper, RealmConfig config, ThreadPool threadPool) { + this(roleMapper, config, buildClient(config), XPackPlugin.getSharedLicenseState(), threadPool); + } + + // for testing + MicrosoftGraphAuthzRealm( + UserRoleMapper roleMapper, + RealmConfig config, + GraphServiceClient client, + XPackLicenseState licenseState, + ThreadPool threadPool + ) { + super(config); + validate(config); + + this.config = config; + this.roleMapper = roleMapper; + this.client = client; + this.licenseState = licenseState; + this.threadPool = threadPool; + this.executionTimeout = config.getSetting(MicrosoftGraphAuthzRealmSettings.EXECUTION_TIMEOUT); + } + + private static void validate(RealmConfig config) { + require(config, MicrosoftGraphAuthzRealmSettings.CLIENT_ID); + require(config, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + require(config, MicrosoftGraphAuthzRealmSettings.TENANT_ID); + } + + private static void require(RealmConfig config, Setting.AffixSetting setting) { + final var value = config.getSetting(setting); + if (value.isEmpty()) { + throw new SettingsException("The configuration setting [" + RealmSettings.getFullSettingKey(config, setting) + "] is required"); + } + } + + @Override + public boolean supports(AuthenticationToken token) { + return false; + } + + @Override + public AuthenticationToken token(ThreadContext context) { + return null; + } + + @Override + public void authenticate(AuthenticationToken token, ActionListener> listener) { + listener.onResponse(AuthenticationResult.notHandled()); + } + + @Override + public void lookupUser(String principal, ActionListener listener) { + if (MICROSOFT_GRAPH_FEATURE.check(licenseState) == false) { + listener.onFailure(LicenseUtils.newComplianceException(MICROSOFT_GRAPH_FEATURE.getName())); + return; + } + + final var runnable = new CancellableRunnable<>(listener, ex -> null, () -> doLookupUser(principal, listener), logger); + threadPool.generic().execute(runnable); + threadPool.schedule(runnable::maybeTimeout, executionTimeout, EsExecutors.DIRECT_EXECUTOR_SERVICE); + } + + private void doLookupUser(String principal, ActionListener listener) { + try { + final var userProperties = fetchUserProperties(client, principal); + final var groups = fetchGroupMembership(client, principal); + + final var userData = new UserRoleMapper.UserData(principal, null, groups, Map.of(), config); + + roleMapper.resolveRoles(userData, listener.delegateFailureAndWrap((l, roles) -> { + final var user = new User( + principal, + roles.toArray(Strings.EMPTY_ARRAY), + userProperties.v1(), + userProperties.v2(), + Map.of(), + true + ); + logger.trace("Authorized user from Microsoft Graph {}", user); + l.onResponse(user); + })); + } catch (Exception e) { + logger.error(Strings.format("Failed to authorize [%s] with MS Graph realm", principal), e); + listener.onFailure(e); + } + } + + private static GraphServiceClient buildClient(RealmConfig config) { + final var clientSecret = config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET); + + final var timeout = config.getSetting(MicrosoftGraphAuthzRealmSettings.HTTP_REQUEST_TIMEOUT); + final var httpClient = new OkHttpClient.Builder().callTimeout(Duration.ofSeconds(timeout.seconds())) + .addInterceptor(new RetryHandler()) + .build(); + + final var credentialProviderBuilder = new ClientSecretCredentialBuilder().clientId( + config.getSetting(MicrosoftGraphAuthzRealmSettings.CLIENT_ID) + ) + .clientSecret(clientSecret.toString()) + .tenantId(config.getSetting(MicrosoftGraphAuthzRealmSettings.TENANT_ID)) + .authorityHost(config.getSetting(MicrosoftGraphAuthzRealmSettings.ACCESS_TOKEN_HOST)) + .httpClient(new OkHttpAsyncHttpClientBuilder(httpClient).build()) + .enableUnsafeSupportLogging() + .enableAccountIdentifierLogging(); + + if (DISABLE_INSTANCE_DISCOVERY) { + credentialProviderBuilder.disableInstanceDiscovery(); + } + final var credentialProvider = credentialProviderBuilder.build(); + + return new GraphServiceClient( + new BaseGraphRequestAdapter( + new AzureIdentityAuthenticationProvider(credentialProvider, Strings.EMPTY_ARRAY, "https://graph.microsoft.com/.default"), + config.getSetting(MicrosoftGraphAuthzRealmSettings.API_HOST), + httpClient + ) + ); + } + + private Tuple fetchUserProperties(GraphServiceClient client, String userId) { + var response = client.users() + .byUserId(userId) + .get(requestConfig -> requestConfig.queryParameters.select = new String[] { "displayName", "mail" }); + + logger.trace("Fetched user with name [{}] and email [{}] from Microsoft Graph", response.getDisplayName(), response.getMail()); + + return Tuple.tuple(response.getDisplayName(), response.getMail()); + } + + private List fetchGroupMembership(GraphServiceClient client, String userId) throws ReflectiveOperationException { + List groups = new ArrayList<>(); + + var groupMembership = client.users().byUserId(userId).transitiveMemberOf().graphGroup().get(requestConfig -> { + requestConfig.queryParameters.select = new String[] { "id" }; + requestConfig.queryParameters.top = PAGE_SIZE; + }); + + var pageIterator = new PageIterator.Builder().client(client) + .collectionPage(groupMembership) + .collectionPageFactory(GroupCollectionResponse::createFromDiscriminatorValue) + .requestConfigurator(requestInfo -> { + requestInfo.addQueryParameter("%24select", new String[] { "id" }); + requestInfo.addQueryParameter("%24top", String.valueOf(PAGE_SIZE)); + return requestInfo; + }) + .processPageItemCallback(group -> { + groups.add(group.getId()); + return true; + }) + .build(); + + pageIterator.iterate(); + + if (logger.isTraceEnabled()) { + logger.trace("Fetched [{}] groups from Microsoft Graph: [{}]", groups.size(), String.join(", ", groups)); + } + + return groups; + } +} diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java new file mode 100644 index 0000000000000..a48e0e11bdef0 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmSettings.java @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.security.authz.microsoft; + +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.xpack.core.security.authc.RealmSettings; + +import java.util.ArrayList; +import java.util.List; + +public class MicrosoftGraphAuthzRealmSettings { + public static final String REALM_TYPE = "microsoft_graph"; + + public static final Setting.AffixSetting CLIENT_ID = RealmSettings.simpleString( + REALM_TYPE, + "client_id", + Setting.Property.NodeScope + ); + + public static final Setting.AffixSetting CLIENT_SECRET = RealmSettings.secureString(REALM_TYPE, "client_secret"); + + public static final Setting.AffixSetting TENANT_ID = RealmSettings.simpleString( + REALM_TYPE, + "tenant_id", + Setting.Property.NodeScope + ); + + public static final Setting.AffixSetting ACCESS_TOKEN_HOST = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "access_token_host", + key -> Setting.simpleString(key, "https://login.microsoftonline.com", Setting.Property.NodeScope) + ); + + public static final Setting.AffixSetting API_HOST = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "graph_host", + key -> Setting.simpleString(key, "https://graph.microsoft.com/v1.0", Setting.Property.NodeScope) + ); + + public static final Setting.AffixSetting HTTP_REQUEST_TIMEOUT = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "http_request_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(10), Setting.Property.NodeScope) + ); + + public static final Setting.AffixSetting EXECUTION_TIMEOUT = Setting.affixKeySetting( + RealmSettings.realmSettingPrefix(REALM_TYPE), + "execution_timeout", + key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(30), Setting.Property.NodeScope) + ); + + public static List> getSettings() { + var settings = new ArrayList>(RealmSettings.getStandardSettings(REALM_TYPE)); + settings.add(CLIENT_ID); + settings.add(CLIENT_SECRET); + settings.add(TENANT_ID); + settings.add(ACCESS_TOKEN_HOST); + settings.add(API_HOST); + settings.add(HTTP_REQUEST_TIMEOUT); + settings.add(EXECUTION_TIMEOUT); + + return settings; + } +} diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml b/x-pack/extras/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml new file mode 100644 index 0000000000000..b05599c660eb2 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/main/plugin-metadata/entitlement-policy.yaml @@ -0,0 +1,5 @@ +okhttp3: + - outbound_network + - manage_threads +okio: + - manage_threads diff --git a/plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension b/x-pack/extras/plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension similarity index 100% rename from plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension rename to x-pack/extras/plugins/microsoft-graph-authz/src/main/resources/META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension diff --git a/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java new file mode 100644 index 0000000000000..2b1c1f8959b89 --- /dev/null +++ b/x-pack/extras/plugins/microsoft-graph-authz/src/test/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzRealmTests.java @@ -0,0 +1,508 @@ +/* + * 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.security.authz.microsoft; + +import com.microsoft.graph.models.Group; +import com.microsoft.graph.models.GroupCollectionResponse; +import com.microsoft.graph.models.odataerrors.MainError; +import com.microsoft.graph.models.odataerrors.ODataError; +import com.microsoft.graph.serviceclient.GraphServiceClient; +import com.microsoft.graph.users.UsersRequestBuilder; +import com.microsoft.graph.users.item.UserItemRequestBuilder; +import com.microsoft.graph.users.item.transitivememberof.TransitiveMemberOfRequestBuilder; +import com.microsoft.graph.users.item.transitivememberof.graphgroup.GraphGroupRequestBuilder; +import com.microsoft.kiota.RequestAdapter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsException; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.license.MockLicenseState; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.MockLog; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.RealmSettings; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.user.User; +import org.junit.After; +import org.junit.Before; + +import java.util.Arrays; +import java.util.Set; + +import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey; +import static org.elasticsearch.xpack.security.authz.microsoft.MicrosoftGraphAuthzRealm.MICROSOFT_GRAPH_FEATURE; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MicrosoftGraphAuthzRealmTests extends ESTestCase { + + private final Settings globalSettings = Settings.builder().put("path.home", createTempDir()).build(); + private final Environment env = TestEnvironment.newEnvironment(globalSettings); + private final ThreadContext threadContext = new ThreadContext(globalSettings); + private final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + private final String realmName = randomAlphaOfLengthBetween(4, 10); + private final String roleName = randomAlphaOfLengthBetween(4, 10); + private final String username = randomAlphaOfLengthBetween(4, 10); + private final String name = randomAlphaOfLengthBetween(4, 10); + private final String email = Strings.format("%s@example.com", randomAlphaOfLengthBetween(4, 10)); + private final String groupId = randomAlphaOfLengthBetween(4, 10); + private final RealmConfig.RealmIdentifier realmId = new RealmConfig.RealmIdentifier( + MicrosoftGraphAuthzRealmSettings.REALM_TYPE, + realmName + ); + + private final String clientId = randomAlphaOfLengthBetween(4, 10); + private final String clientSecret = randomAlphaOfLengthBetween(4, 10); + private final String tenantId = randomAlphaOfLengthBetween(4, 10); + + private static final AuthenticationToken fakeToken = new AuthenticationToken() { + @Override + public String principal() { + fail("Should never be called"); + return null; + } + + @Override + public Object credentials() { + fail("Should never be called"); + return null; + } + + @Override + public void clearCredentials() { + fail("Should never be called"); + } + }; + + @Before + public void setUp() throws Exception { + super.setUp(); + + final var logger = LogManager.getLogger(MicrosoftGraphAuthzRealm.class); + Loggers.setLevel(logger, Level.TRACE); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + terminate(threadPool); + } + + public void testLookupUser() { + try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "Fetch user properties", + MicrosoftGraphAuthzRealm.class.getName(), + Level.TRACE, + Strings.format("Fetched user with name [%s] and email [%s] from Microsoft Graph", name, email) + ) + ); + + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "Fetch group membership", + MicrosoftGraphAuthzRealm.class.getName(), + Level.TRACE, + Strings.format("Fetched [1] groups from Microsoft Graph: [%s]", groupId) + ) + ); + + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); + + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + when(client.getRequestAdapter()).thenReturn(mock(RequestAdapter.class)); + + final var userRequestBuilder = mockGetUser(client); + when(userRequestBuilder.get(any())).thenReturn(user(name, email)); + + final var graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership(groupId)); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture(); + realm.lookupUser(username, future); + + final var user = future.actionGet(); + assertThat(user.principal(), equalTo(username)); + assertThat(user.fullName(), equalTo(name)); + assertThat(user.email(), equalTo(email)); + assertThat(user.roles(), arrayContaining(roleName)); + + mockLog.assertAllExpectationsMatched(); + } + } + + public void testHandleGetUserPropertiesError() { + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); + + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + final var requestAdapter = mock(RequestAdapter.class); + when(client.getRequestAdapter()).thenReturn(requestAdapter); + + final var userItemRequestBuilder = mockGetUser(client); + when(userItemRequestBuilder.get(any())).thenThrow(graphError("bad stuff happened")); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture(); + + try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "Log exception", + MicrosoftGraphAuthzRealm.class.getName(), + Level.ERROR, + Strings.format("Failed to authorize [%s] with MS Graph realm", username) + ) + ); + + realm.lookupUser(username, future); + final var thrown = assertThrows(ODataError.class, future::actionGet); + assertThat(thrown.getMessage(), equalTo("bad stuff happened")); + + mockLog.assertAllExpectationsMatched(); + } + } + + public void testHandleGetGroupMembershipError() { + final var roleMapper = mockRoleMapper(Set.of(groupId), Set.of(roleName)); + + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + when(client.getRequestAdapter()).thenReturn(mock(RequestAdapter.class)); + + final var userRequestBuilder = mockGetUser(client); + when(userRequestBuilder.get(any())).thenReturn(user(name, email)); + + final var graphGroupRequestBuilder = mockGetGroupMembership(userRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenThrow(graphError("bad stuff happened")); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture(); + + try (var mockLog = MockLog.capture(MicrosoftGraphAuthzRealm.class)) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "Log exception", + MicrosoftGraphAuthzRealm.class.getName(), + Level.ERROR, + Strings.format("Failed to authorize [%s] with MS Graph realm", username) + ) + ); + + realm.lookupUser(username, future); + final var thrown = assertThrows(ODataError.class, future::actionGet); + assertThat(thrown.getMessage(), equalTo("bad stuff happened")); + + mockLog.assertAllExpectationsMatched(); + } + } + + public void testGroupMembershipPagination() { + final var groupId2 = randomAlphaOfLengthBetween(4, 10); + final var groupId3 = randomAlphaOfLengthBetween(4, 10); + + final var roleMapper = mockRoleMapper(Set.of(groupId, groupId2, groupId3), Set.of(roleName)); + + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + final var requestAdapter = mock(RequestAdapter.class); + when(client.getRequestAdapter()).thenReturn(requestAdapter); + + final var userItemRequestBuilder = mockGetUser(client); + when(userItemRequestBuilder.get(any())).thenReturn(user(name, email)); + + final var groupMembership1 = groupMembership(groupId); + groupMembership1.setOdataNextLink("http://localhost:12345/page2"); + + final var groupMembership2 = groupMembership(groupId2); + groupMembership2.setOdataNextLink("http://localhost:12345/page3"); + + final var groupMembership3 = groupMembership(groupId3); + + final var graphGroupRequestBuilder = mockGetGroupMembership(userItemRequestBuilder); + when(graphGroupRequestBuilder.get(any())).thenReturn(groupMembership1); + when(requestAdapter.send(any(), any(), any())).thenReturn(groupMembership2, groupMembership3); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture(); + realm.lookupUser(username, future); + final var user = future.actionGet(); + assertThat(user.principal(), equalTo(username)); + assertThat(user.fullName(), equalTo(name)); + assertThat(user.email(), equalTo(email)); + assertThat(user.roles(), arrayContaining(roleName)); + } + + public void testLicenseCheck() { + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(false); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture(); + realm.lookupUser(username, future); + final var thrown = assertThrows(ElasticsearchSecurityException.class, future::actionGet); + assertThat(thrown.getMessage(), equalTo("current license is non-compliant for [microsoft_graph]")); + } + + public void testClientIdSettingRequired() { + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = realmSettings().put( + getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), + randomBoolean() ? "" : null + ).build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(true); + + final var thrown = assertThrows( + SettingsException.class, + () -> new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool) + ); + assertThat( + thrown.getMessage(), + equalTo( + Strings.format( + "The configuration setting [%s] is required", + getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID) + ) + ) + ); + } + + public void testClientSecretSettingRequired() { + final var roleMapper = mock(UserRoleMapper.class); + final var secureSettings = new MockSecureSettings(); + if (randomBoolean()) { + secureSettings.setString(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET), ""); + } + final var realmSettings = Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), clientId) + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), tenantId) + .setSecureSettings(secureSettings) + .build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(true); + + final var thrown = assertThrows( + SettingsException.class, + () -> new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool) + ); + assertThat( + thrown.getMessage(), + equalTo( + Strings.format( + "The configuration setting [%s] is required", + getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET) + ) + ) + ); + } + + public void testTenantIdSettingRequired() { + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = realmSettings().put( + getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), + randomBoolean() ? "" : null + ).build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(true); + + final var thrown = assertThrows( + SettingsException.class, + () -> new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool) + ); + assertThat( + thrown.getMessage(), + equalTo( + Strings.format( + "The configuration setting [%s] is required", + getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID) + ) + ) + ); + } + + public void testSupportsAlwaysReturnsFalse() { + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + + assertThat(realm.supports(fakeToken), equalTo(false)); + } + + public void testTokenAlwaysReturnsNull() { + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + assertThat(realm.token(threadContext), equalTo(null)); + } + + public void testAuthenticateAlwaysReturnsNotHandled() { + final var roleMapper = mock(UserRoleMapper.class); + final var realmSettings = realmSettings().build(); + + final var config = new RealmConfig(realmId, realmSettings, env, threadContext); + final var client = mock(GraphServiceClient.class); + + final var licenseState = mockLicense(true); + + final var realm = new MicrosoftGraphAuthzRealm(roleMapper, config, client, licenseState, threadPool); + final var future = new PlainActionFuture>(); + realm.authenticate(fakeToken, future); + final var result = future.actionGet(); + assertThat(result, equalTo(AuthenticationResult.notHandled())); + } + + private UserRoleMapper mockRoleMapper(Set expectedGroups, Set rolesToReturn) { + final var roleMapper = mock(UserRoleMapper.class); + doAnswer(invocation -> { + var userData = (UserRoleMapper.UserData) invocation.getArguments()[0]; + assertEquals(userData.getGroups(), expectedGroups); + @SuppressWarnings("unchecked") + var listener = (ActionListener>) invocation.getArguments()[1]; + listener.onResponse(rolesToReturn); + return null; + }).when(roleMapper).resolveRoles(any(), any()); + + return roleMapper; + } + + private Settings.Builder realmSettings() { + final var secureSettings = new MockSecureSettings(); + secureSettings.setString(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_SECRET), clientSecret); + + return Settings.builder() + .put(globalSettings) + .put(getFullSettingKey(realmId, RealmSettings.ORDER_SETTING), 0) + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.CLIENT_ID), clientId) + .put(getFullSettingKey(realmName, MicrosoftGraphAuthzRealmSettings.TENANT_ID), tenantId) + .setSecureSettings(secureSettings); + } + + private XPackLicenseState mockLicense(boolean msGraphAllowed) { + final var licenseState = MockLicenseState.createMock(); + when(licenseState.isAllowed(eq(MICROSOFT_GRAPH_FEATURE))).thenReturn(msGraphAllowed); + return licenseState; + } + + private UserItemRequestBuilder mockGetUser(GraphServiceClient client) { + final var userRequestBuilder = mock(UsersRequestBuilder.class); + final var userItemRequestBuilder = mock(UserItemRequestBuilder.class); + + when(client.users()).thenReturn(userRequestBuilder); + when(userRequestBuilder.byUserId(eq(username))).thenReturn(userItemRequestBuilder); + + return userItemRequestBuilder; + } + + private GraphGroupRequestBuilder mockGetGroupMembership(UserItemRequestBuilder userItemRequestBuilder) { + final var memberOfRequestBuilder = mock(TransitiveMemberOfRequestBuilder.class); + final var graphGroupRequestBuilder = mock(GraphGroupRequestBuilder.class); + + when(userItemRequestBuilder.transitiveMemberOf()).thenReturn(memberOfRequestBuilder); + when(memberOfRequestBuilder.graphGroup()).thenReturn(graphGroupRequestBuilder); + + return graphGroupRequestBuilder; + } + + private com.microsoft.graph.models.User user(String name, String email) { + final var msUser = new com.microsoft.graph.models.User(); + msUser.setDisplayName(name); + msUser.setMail(email); + + return msUser; + } + + private GroupCollectionResponse groupMembership(String... groupIds) { + final var groupMembership = new GroupCollectionResponse(); + groupMembership.setValue(Arrays.stream(groupIds).map(id -> { + var group = new Group(); + group.setId(id); + return group; + }).toList()); + return groupMembership; + } + + private ODataError graphError(String message) { + final var error = new MainError(); + error.setCode("badRequest"); + error.setMessage(message); + + final var graphError = new ODataError(); + graphError.setError(error); + + return graphError; + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java new file mode 100644 index 0000000000000..f802923ce3c05 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/CancellableRunnable.java @@ -0,0 +1,76 @@ +/* + * 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.core.security.support; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.ElasticsearchTimeoutException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.util.concurrent.AbstractRunnable; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +/** + * A runnable that allows us to terminate and call the listener. We use this as a runnable can + * be queued and not executed for a long time or ever and this causes user requests to appear + * to hang. In these cases at least we can provide a response. + */ +public class CancellableRunnable extends AbstractRunnable { + + private final Runnable in; + private final ActionListener listener; + private final Function defaultValue; + private final Logger logger; + private final AtomicReference state = new AtomicReference<>(RunnableState.AWAITING_EXECUTION); + + public CancellableRunnable(ActionListener listener, Function defaultValue, Runnable in, Logger logger) { + this.listener = listener; + this.defaultValue = Objects.requireNonNull(defaultValue); + this.in = in; + this.logger = logger; + } + + @Override + public void onFailure(Exception e) { + logger.error("execution of cancellable runnable failed", e); + final T result = defaultValue.apply(e); + listener.onResponse(result); + } + + @Override + protected void doRun() throws Exception { + if (state.compareAndSet(RunnableState.AWAITING_EXECUTION, RunnableState.EXECUTING)) { + in.run(); + } else { + logger.trace("skipping execution of cancellable runnable as the current state is [{}]", state.get()); + } + } + + @Override + public void onRejection(Exception e) { + listener.onFailure(e); + } + + /** + * If the execution of this runnable has not already started, the runnable is cancelled and we pass an exception to the user + * listener + */ + public void maybeTimeout() { + if (state.compareAndSet(RunnableState.AWAITING_EXECUTION, RunnableState.TIMED_OUT)) { + logger.warn("skipping execution of cancellable runnable as it has been waiting for execution too long"); + listener.onFailure(new ElasticsearchTimeoutException("timed out waiting for execution of cancellable runnable")); + } + } + + private enum RunnableState { + AWAITING_EXECUTION, + EXECUTING, + TIMED_OUT + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java similarity index 84% rename from x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java rename to x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java index aa8e5610dbc23..ef96461b6be07 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/CancellableLdapRunnableTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/support/CancellableRunnableTests.java @@ -4,14 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -package org.elasticsearch.xpack.security.authc.ldap; +package org.elasticsearch.xpack.core.security.support; import org.elasticsearch.ElasticsearchTimeoutException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionTestUtils; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.security.user.User; -import org.elasticsearch.xpack.security.authc.ldap.LdapRealm.CancellableLdapRunnable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; @@ -22,11 +21,11 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.sameInstance; -public class CancellableLdapRunnableTests extends ESTestCase { +public class CancellableRunnableTests extends ESTestCase { public void testTimingOutARunnable() { AtomicReference exceptionAtomicReference = new AtomicReference<>(); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> { + final CancellableRunnable runnable = new CancellableRunnable<>(ActionListener.wrap(user -> { throw new AssertionError("onResponse should not be called"); }, exceptionAtomicReference::set), e -> null, () -> { throw new AssertionError("runnable should not be executed"); }, logger); @@ -40,7 +39,7 @@ public void testTimingOutARunnable() { public void testCallTimeOutAfterRunning() { final AtomicBoolean ran = new AtomicBoolean(false); final AtomicBoolean listenerCalled = new AtomicBoolean(false); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> { + final CancellableRunnable runnable = new CancellableRunnable<>(ActionListener.wrap(user -> { listenerCalled.set(true); throw new AssertionError("onResponse should not be called"); }, e -> { @@ -59,7 +58,7 @@ public void testCallTimeOutAfterRunning() { public void testRejectingExecution() { AtomicReference exceptionAtomicReference = new AtomicReference<>(); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(ActionListener.wrap(user -> { + final CancellableRunnable runnable = new CancellableRunnable<>(ActionListener.wrap(user -> { throw new AssertionError("onResponse should not be called"); }, exceptionAtomicReference::set), e -> null, () -> { throw new AssertionError("runnable should not be executed"); }, logger); @@ -75,7 +74,7 @@ public void testTimeoutDuringExecution() throws InterruptedException { final CountDownLatch timeoutCalledLatch = new CountDownLatch(1); final CountDownLatch runningLatch = new CountDownLatch(1); final ActionListener listener = ActionTestUtils.assertNoFailureListener(user -> listenerCalledLatch.countDown()); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(listener, e -> null, () -> { + final CancellableRunnable runnable = new CancellableRunnable<>(listener, e -> null, () -> { runningLatch.countDown(); try { timeoutCalledLatch.await(); @@ -98,7 +97,7 @@ public void testExceptionInRunnable() { AtomicReference resultRef = new AtomicReference<>(); final ActionListener listener = ActionTestUtils.assertNoFailureListener(resultRef::set); String defaultValue = randomAlphaOfLengthBetween(2, 10); - final CancellableLdapRunnable runnable = new CancellableLdapRunnable<>(listener, e -> defaultValue, () -> { + final CancellableRunnable runnable = new CancellableRunnable<>(listener, e -> defaultValue, () -> { throw new RuntimeException("runnable intentionally failed"); }, logger); diff --git a/x-pack/plugin/security/build.gradle b/x-pack/plugin/security/build.gradle index fb798e25f1a34..987e489fbf09f 100644 --- a/x-pack/plugin/security/build.gradle +++ b/x-pack/plugin/security/build.gradle @@ -66,7 +66,7 @@ dependencies { } api "org.slf4j:slf4j-api:${versions.slf4j}" runtimeOnly "org.slf4j:slf4j-nop:${versions.slf4j}" // workaround for https://github.com/elastic/elasticsearch/issues/93714 - // api "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" see above + // api "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}" see above api "org.apache.httpcomponents:httpclient:${versions.httpclient}" api "org.apache.httpcomponents:httpcore:${versions.httpcore}" @@ -80,6 +80,7 @@ dependencies { // Dependencies for oidc api "com.nimbusds:oauth2-oidc-sdk:11.22.2" + runtimeOnly "com.nimbusds:content-type:2.3" api project(path: xpackModule('security:lib:nimbus-jose-jwt-modified'), configuration: 'shadow') if (isEclipse) { /* @@ -183,6 +184,7 @@ tasks.named("dependencyLicenses").configure { mapping from: /http.*/, to: 'httpclient' mapping from: /bc.*/, to: 'bouncycastle' mapping from: /failureaccess.*/, to: 'guava' + mapping from: 'content-type', to: 'nimbus' } tasks.named("forbiddenPatterns").configure { @@ -414,8 +416,6 @@ tasks.named("thirdPartyAudit").configure { 'javax.xml.bind.UnmarshallerHandler', // Optional dependency of oauth2-oidc-sdk that we don't need since we do not support AES-SIV for JWE 'org.cryptomator.siv.SivMode', - 'com.nimbusds.common.contenttype.ContentType', - 'com.nimbusds.common.contenttype.ContentType$Parameter', 'javax.activation.ActivationDataFlavor', 'javax.activation.DataContentHandler', 'javax.activation.DataHandler', diff --git a/x-pack/plugin/security/licenses/nimbus-LICENSE.txt b/x-pack/plugin/security/licenses/nimbus-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/x-pack/plugin/security/licenses/nimbus-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/x-pack/plugin/security/licenses/nimbus-NOTICE.txt b/x-pack/plugin/security/licenses/nimbus-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle index 847179ff60e6a..f949902c68079 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/build.gradle @@ -4,5 +4,11 @@ dependencies { javaRestTestImplementation project(':x-pack:plugin:core') javaRestTestImplementation project(':x-pack:plugin:security') javaRestTestImplementation testArtifact(project(":x-pack:plugin:security:qa:saml-rest-tests"), "javaRestTest") - clusterPlugins project(':plugins:microsoft-graph-authz') + clusterPlugins project(':x-pack:extras:plugins:microsoft-graph-authz') + clusterModules project(":modules:analysis-common") +} + +tasks.named("javaRestTest").configure { + // disable tests in FIPS mode as we need to use a custom truststore containing the certs used in MicrosoftGraphHttpFixture + buildParams.withFipsEnabledOnly(it) } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java index da8b4e198f61d..d003e9dec9c4a 100644 --- a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphAuthzPluginIT.java @@ -7,21 +7,30 @@ package org.elasticsearch.xpack.security.authz.microsoft; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionTestUtils; +import org.elasticsearch.action.support.GroupedActionListener; +import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.Request; +import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.core.PathUtils; +import org.elasticsearch.test.TestTrustStore; import org.elasticsearch.test.XContentTestUtils; import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.test.cluster.local.model.User; import org.elasticsearch.test.cluster.util.resource.Resource; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.ObjectPath; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.security.authc.saml.SamlIdpMetadataBuilder; import org.elasticsearch.xpack.security.authc.saml.SamlResponseBuilder; +import org.junit.Before; import org.junit.ClassRule; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; @@ -32,23 +41,62 @@ import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.util.Base64; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; public class MicrosoftGraphAuthzPluginIT extends ESRestTestCase { + + private static final String TENANT_ID = "tenant-id"; + private static final String CLIENT_ID = "client_id"; + private static final String CLIENT_SECRET = "client_secret"; + private static final String USERNAME = "Thor"; + private static final String EXPECTED_GROUP = "test_group"; + + private static final List TEST_USERS = List.of( + new MicrosoftGraphHttpFixture.TestUser( + USERNAME, + "Thor Odinson", + "thor@oldap.test.elasticsearch.com", + List.of("unmapped-group-1", "unmapped-group-2", "unmapped-group-3", EXPECTED_GROUP), + List.of("microsoft_graph_user") + ), + new MicrosoftGraphHttpFixture.TestUser( + "User2", + "User 2", + "user2@example.com", + List.of(EXPECTED_GROUP), + List.of("microsoft_graph_user") + ), + new MicrosoftGraphHttpFixture.TestUser("User3", "User 3", "user3@example.com", List.of(), List.of()) + ); + + private static final MicrosoftGraphHttpFixture graphFixture = new MicrosoftGraphHttpFixture( + TENANT_ID, + CLIENT_ID, + CLIENT_SECRET, + TEST_USERS, + 3 + ); + public static ElasticsearchCluster cluster = initTestCluster(); + private static final TestTrustStore trustStore = new TestTrustStore( + () -> MicrosoftGraphAuthzPluginIT.class.getClassLoader().getResourceAsStream("server/cert.pem") + ); + @ClassRule - public static TestRule ruleChain = RuleChain.outerRule(cluster); + public static TestRule ruleChain = RuleChain.outerRule(graphFixture).around(trustStore).around(cluster); private static final String IDP_ENTITY_ID = "http://idp.example.org/"; private static ElasticsearchCluster initTestCluster() { return ElasticsearchCluster.local() + .module("analysis-common") .setting("xpack.security.enabled", "true") .setting("xpack.license.self_generated.type", "trial") .setting("xpack.security.authc.token.enabled", "true") @@ -67,6 +115,17 @@ private static ElasticsearchCluster initTestCluster() { .setting("xpack.security.authc.realms.saml.saml1.sp.logout", "http://logout/default") .setting("xpack.security.authc.realms.saml.saml1.authorization_realms", "microsoft_graph1") .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.order", "2") + .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.client_id", CLIENT_ID) + .keystore("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.client_secret", CLIENT_SECRET) + .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.tenant_id", TENANT_ID) + .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.graph_host", () -> graphFixture.getBaseUrl() + "/v1.0") + .setting("xpack.security.authc.realms.microsoft_graph.microsoft_graph1.access_token_host", graphFixture::getBaseUrl) + .setting("logger.org.elasticsearch.xpack.security.authz.microsoft", "TRACE") + .setting("logger.com.microsoft", "TRACE") + .setting("logger.com.azure", "TRACE") + .systemProperty("javax.net.ssl.trustStore", () -> trustStore.getTrustStorePath().toString()) + .systemProperty("javax.net.ssl.trustStoreType", "jks") + .systemProperty("tests.azure.credentials.disable_instance_discovery", "true") .build(); } @@ -80,6 +139,35 @@ private static String getIDPMetadata() { return null; } + @Before + public void setupRoleMapping() throws IOException { + Request request = new Request("PUT", "/_security/role_mapping/thor-kibana"); + request.setJsonEntity( + Strings.toString( + XContentBuilder.builder(XContentType.JSON.xContent()) + .startObject() + .array("roles", new String[] { "microsoft_graph_user" }) + .field("enabled", true) + .startObject("rules") + .startArray("all") + .startObject() + .startObject("field") + .field("realm.name", "microsoft_graph1") + .endObject() + .endObject() + .startObject() + .startObject("field") + .field("groups", EXPECTED_GROUP) + .endObject() + .endObject() + .endArray() // "all" + .endObject() // "rules" + .endObject() + ) + ); + adminClient().performRequest(request); + } + @Override protected String getTestRestCluster() { return cluster.getHttpAddresses(); @@ -96,14 +184,54 @@ protected Settings restClientSettings() { return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); } + @Override + protected void configureClient(RestClientBuilder builder, Settings settings) throws IOException { + super.configureClient(builder, settings); + + builder.setRequestConfigCallback(requestConfigBuilder -> { + requestConfigBuilder.setSocketTimeout(-1); + return requestConfigBuilder; + }); + } + @Override protected boolean shouldConfigureProjects() { return false; } public void testAuthenticationSuccessful() throws Exception { - final String username = randomAlphaOfLengthBetween(4, 12); - samlAuthWithMicrosoftGraphAuthz(username, getSamlAssertionJsonBodyString(username)); + final var listener = new PlainActionFuture>(); + samlAuthWithMicrosoftGraphAuthz(getSamlAssertionJsonBodyString(USERNAME), listener); + final var resp = listener.get(); + List roles = new XContentTestUtils.JsonMapView(resp).get("authentication.roles"); + assertThat(resp.get("username"), equalTo(USERNAME)); + assertThat(roles, contains("microsoft_graph_user")); + assertThat(ObjectPath.evaluate(resp, "authentication.authentication_realm.name"), equalTo("saml1")); + } + + public void testConcurrentAuthentication() throws Exception { + final var concurrentLogins = 3; + + final var resultsListener = new PlainActionFuture>>(); + final var groupedListener = new GroupedActionListener<>(TEST_USERS.size() * concurrentLogins, resultsListener); + for (int i = 0; i < concurrentLogins; i++) { + for (var user : TEST_USERS) { + samlAuthWithMicrosoftGraphAuthz(getSamlAssertionJsonBodyString(user.username()), groupedListener); + } + } + final var allResponses = resultsListener.get(); + + assertThat(allResponses.size(), equalTo(TEST_USERS.size() * concurrentLogins)); + for (var user : TEST_USERS) { + var userResponses = allResponses.stream().filter(r -> r.get("username").equals(user.username())).toList(); + assertThat(userResponses.size(), equalTo(concurrentLogins)); + + for (var response : userResponses) { + final List roles = new XContentTestUtils.JsonMapView(response).get("authentication.roles"); + assertThat(roles, equalTo(user.roles())); + assertThat(ObjectPath.evaluate(response, "authentication.authentication_realm.name"), equalTo("saml1")); + } + } } private String getSamlAssertionJsonBodyString(String username) throws Exception { @@ -120,15 +248,10 @@ private String getSamlAssertionJsonBodyString(String username) throws Exception return Strings.toString(JsonXContent.contentBuilder().map(body)); } - private void samlAuthWithMicrosoftGraphAuthz(String username, String samlAssertion) throws Exception { + private void samlAuthWithMicrosoftGraphAuthz(String samlAssertion, ActionListener> listener) { var req = new Request("POST", "_security/saml/authenticate"); req.setJsonEntity(samlAssertion); - var resp = entityAsMap(client().performRequest(req)); - List roles = new XContentTestUtils.JsonMapView(entityAsMap(client().performRequest(req))).get("authentication.roles"); - assertThat(resp.get("username"), equalTo(username)); - // TODO add check for mapped groups and roles when available - assertThat(roles, empty()); - assertThat(ObjectPath.evaluate(resp, "authentication.authentication_realm.name"), equalTo("saml1")); + client().performRequestAsync(req, ActionTestUtils.wrapAsRestResponseListener(listener.map(ESRestTestCase::entityAsMap))); } } diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java new file mode 100644 index 0000000000000..d557fd7123332 --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/authz/microsoft/MicrosoftGraphHttpFixture.java @@ -0,0 +1,296 @@ +/* + * 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.security.authz.microsoft; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.ssl.KeyStoreUtil; +import org.elasticsearch.common.ssl.PemUtils; +import org.elasticsearch.core.Strings; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.rest.RestUtils; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentType; +import org.junit.rules.ExternalResource; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; + +public class MicrosoftGraphHttpFixture extends ExternalResource { + + private static final Logger logger = LogManager.getLogger(MicrosoftGraphHttpFixture.class); + + private final String tenantId; + private final String clientId; + private final String clientSecret; + private final List users; + private final int groupsPageSize; + private final String jwt = "test jwt"; + + private final AtomicInteger loginCount = new AtomicInteger(0); + private final AtomicInteger getUserPropertiesCount = new AtomicInteger(0); + private final AtomicInteger getGroupMembershipCount = new AtomicInteger(0); + + private HttpsServer server; + + public MicrosoftGraphHttpFixture(String tenantId, String clientId, String clientSecret, List users, int groupsPageSize) { + this.tenantId = tenantId; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.users = users; + this.groupsPageSize = groupsPageSize; + } + + @Override + protected void before() throws Throwable { + final var certificate = PemUtils.readCertificates( + List.of(Path.of(getClass().getClassLoader().getResource("server/cert.pem").toURI())) + ).getFirst(); + final var key = PemUtils.readPrivateKey(Path.of(getClass().getClassLoader().getResource("server/cert.key").toURI()), () -> null); + final var sslContext = SSLContext.getInstance("TLS"); + sslContext.init( + new KeyManager[] { KeyStoreUtil.createKeyManager(new Certificate[] { certificate }, key, null) }, + null, + new SecureRandom() + ); + + server = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + + registerGetAccessTokenHandler(); + + for (TestUser user : users) { + registerGetUserHandler(user); + registerGetUserMembershipHandler(user); + } + + server.createContext("/", exchange -> { + logger.warn("Unhandled request for [{}]", exchange.getRequestURI()); + exchange.sendResponseHeaders(RestStatus.NOT_IMPLEMENTED.getStatus(), 0); + exchange.close(); + }); + server.start(); + logger.info("Started server on port [{}]", server.getAddress().getPort()); + } + + @Override + protected void after() { + server.stop(0); + } + + public String getBaseUrl() { + return "https://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); + } + + private void registerGetAccessTokenHandler() { + server.createContext("/" + tenantId + "/oauth2/v2.0/token", exchange -> { + logger.info("Received access token request"); + final var callCount = loginCount.incrementAndGet(); + + if (callCount == 1) { + graphError(exchange, RestStatus.GATEWAY_TIMEOUT, "Gateway timed out"); + return; + } + + if (exchange.getRequestMethod().equals("POST") == false) { + graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected POST request"); + return; + } + + final var requestBody = Streams.copyToString(new InputStreamReader(exchange.getRequestBody(), Charset.defaultCharset())); + final var formFields = new HashMap(); + RestUtils.decodeQueryString(requestBody, 0, formFields); + + if (formFields.get("grant_type").equals("client_credentials") == false) { + graphError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Grant Type: %s", formFields.get("grant_type"))); + return; + } + if (formFields.get("client_id").equals(clientId) == false) { + graphError(exchange, RestStatus.BAD_REQUEST, Strings.format("Unexpected Client ID: %s", formFields.get("client_id"))); + return; + } + if (formFields.get("client_secret").equals(clientSecret) == false) { + graphError( + exchange, + RestStatus.BAD_REQUEST, + Strings.format("Unexpected Client Secret: %s", formFields.get("client_secret")) + ); + return; + } + if (formFields.get("scope").contains("https://graph.microsoft.com/.default") == false) { + graphError( + exchange, + RestStatus.BAD_REQUEST, + Strings.format("Missing required https://graph.microsoft.com/.default scope: [%s]", formFields.get("scope")) + ); + return; + } + + final var token = XContentBuilder.builder(XContentType.JSON.xContent()); + token.startObject(); + token.field("access_token", jwt); + token.field("expires_in", 86400L); + token.field("ext_expires_in", 86400L); + token.field("token_type", "Bearer"); + token.endObject(); + + var responseBytes = BytesReference.bytes(token); + + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(RestStatus.OK.getStatus(), responseBytes.length()); + responseBytes.writeTo(exchange.getResponseBody()); + exchange.close(); + }); + } + + private void registerGetUserHandler(TestUser user) { + server.createContext("/v1.0/users/" + user.username(), exchange -> { + logger.info("Received get user properties request [{}]", exchange.getRequestURI()); + final var callCount = getUserPropertiesCount.incrementAndGet(); + + if (exchange.getRequestMethod().equals("GET") == false) { + graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); + return; + } + + final var authorization = exchange.getRequestHeaders().getFirst("Authorization"); + if (authorization.equals("Bearer " + jwt) == false) { + graphError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization)); + return; + } + + if (exchange.getRequestURI().getQuery().contains("$select=displayName,mail") == false) { + graphError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); + return; + } + + // ensure the client retries temporary errors + if (callCount == 1) { + graphError(exchange, RestStatus.GATEWAY_TIMEOUT, "Gateway timed out"); + return; + } else if (callCount == 2) { + graphError(exchange, RestStatus.TOO_MANY_REQUESTS, "Too many requests"); + return; + } + + var userProperties = XContentBuilder.builder(XContentType.JSON.xContent()); + userProperties.startObject(); + userProperties.field("displayName", user.displayName()); + userProperties.field("mail", user.email()); + userProperties.endObject(); + + var responseBytes = BytesReference.bytes(userProperties); + + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(RestStatus.OK.getStatus(), responseBytes.length()); + responseBytes.writeTo(exchange.getResponseBody()); + + exchange.close(); + }); + } + + private void registerGetUserMembershipHandler(TestUser user) { + final var skipToken = UUID.randomUUID().toString(); + + server.createContext("/v1.0/users/" + user.username() + "/transitiveMemberOf", exchange -> { + logger.info("Received get user membership request [{}]", exchange.getRequestURI()); + final var callCount = getGroupMembershipCount.incrementAndGet(); + + if (callCount == 1) { + graphError(exchange, RestStatus.GATEWAY_TIMEOUT, "Gateway timed out"); + return; + } + + if (exchange.getRequestMethod().equals("GET") == false) { + graphError(exchange, RestStatus.METHOD_NOT_ALLOWED, "Expected GET request"); + return; + } + + final var authorization = exchange.getRequestHeaders().getFirst("Authorization"); + if (authorization.equals("Bearer " + jwt) == false) { + graphError(exchange, RestStatus.UNAUTHORIZED, Strings.format("Wrong Authorization header: %s", authorization)); + return; + } + + if (exchange.getRequestURI().getQuery().contains("$select=id") == false) { + // this test server only returns `id`s, so if the client is expecting other fields, it won't work anyway + graphError(exchange, RestStatus.BAD_REQUEST, "Must filter fields using $select"); + return; + } + + String nextLink = null; + Object[] groups; + + // return multiple pages of results, to ensure client correctly supports paging + if (exchange.getRequestURI().getQuery().contains("$skiptoken")) { + groups = user.groups().stream().skip(groupsPageSize).map(id -> Map.of("id", id)).toArray(); + } else { + groups = user.groups().stream().limit(groupsPageSize).map(id -> Map.of("id", id)).toArray(); + if (user.groups().size() > groupsPageSize) { + nextLink = getBaseUrl() + exchange.getRequestURI().toString() + "&$skiptoken=" + skipToken; + } + } + + final var groupMembership = XContentBuilder.builder(XContentType.JSON.xContent()); + groupMembership.startObject(); + groupMembership.field("@odata.nextLink", nextLink); + groupMembership.array("value", groups); + groupMembership.endObject(); + + var responseBytes = BytesReference.bytes(groupMembership); + + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(RestStatus.OK.getStatus(), responseBytes.length()); + responseBytes.writeTo(exchange.getResponseBody()); + + exchange.close(); + }); + } + + // attempt to comply with https://learn.microsoft.com/en-us/graph/errors + private void graphError(HttpExchange exchange, RestStatus statusCode, String message) throws IOException { + logger.warn(message); + + final var errorResponse = XContentBuilder.builder(XContentType.JSON.xContent()); + errorResponse.startObject(); + errorResponse.startObject("error"); + errorResponse.field("code", statusCode.toString()); + errorResponse.field("message", message); + errorResponse.endObject(); + errorResponse.endObject(); + + final var responseBytes = BytesReference.bytes(errorResponse); + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(statusCode.getStatus(), responseBytes.length()); + responseBytes.writeTo(exchange.getResponseBody()); + + exchange.close(); + } + + public record TestUser(String username, String displayName, String email, List groups, List roles) {} +} diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key new file mode 100644 index 0000000000000..29efd3a6913a0 --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCswbyZDtaghZXs +Phs1+lqCnq5HmRT2P6Drrs9bJlABeql29IhzdHOGLr+lTMhKOUpHuphgC31qbf/G +vQLS65qdOzTjNfLv93+Jj0gp4S7Q6eRZvn1ihUgzECHazTYwIlzVs4sFPm5i2fQb +DK7W6zQm+h9r6GjCYj01OeIAe7rbRI9Ar+svuHGfZnaQHzLZlfYkkM2bCaXBgKWV +wmEUmwMW+IMOPCrVm+gk1MDbGnu9KtY/LqrJcddsqOdkK8qJ0Lpchg3zlP4qIzbm +WRyTUIy1USbcazjuC/vMmN4fr/Xr0Jrhi4Rw8l2LGdyA8qnqtKYTqMzo3uv1ESlE +R8EAZDUbAgMBAAECggEAY8lYRdSTVo8y5Q2OrCQa6b38jvC2cfKY4enMbir4JZKT +lllzA7VtEUGpgzKRsoXbCQmYAEpCvBojlskQe4KJgW50gxVjaQa9zVhM55vhbdzc +AJaOWD0CUjRsSbUlKrJ+ixW1JGdGXaTlYkZ2K0AalLT/N1Y8RKN4FWmEyKCvcvz4 +0XzOIVmG+HqcNURamXTxMKbj1yzi5goue2/iP2kMFo8sHxRsGvvV4PWo6JrktE2y +47oiH42lpSIcpLSE8z/csLbMTw/Q/pPQAYqyvEJHU22uhac1XmMqFHWNSpQZq6gr +46t8YQ3FJSN8UrZf1h1mdvLlK/sgPEvCQa6TrCq4GQKBgQDbl0M/4gJZhgpvBuCC +aratamWcFoa/pu1/JoQhPXLv6uGwB/cFhJUVr0ZoI5KPFJr9SG4kQ/eEPywkY2qT +mGPVlVmGOeJa1VK8TRUTzkEFTYWytUepACM2//LiWvzABciO8SxWgNZrmUKghQTN +d989b8edy0ti6y7lHpkTyawVXQKBgQDJZo7X6K+6cyuXORApv98cU5C3vEVtBp/c +QfU/rRj/YXAbYFhKIS5rF/Gfg2YoDa5YguMxGsIPzYwdTI5gGGgSgolapz3fr22q +edCPaFg8qO64pIii+Ar4lx4k1IyNtpJ+nvlam7sI9yGzksrVazsWhpaSKX8xGd7r +9ZSr/c8U1wKBgGWl+pJay52nR7MnasvUHCXgR5LedpfG7M9cA/PjHw5iGwDCXx2l +xuFX1m6kcNZcwnYWji2pbK1CFOvvPUl/VE9tKBjTOK21a+wQfn5BjqWmwgn8kmRv +1N1D06nmVnOI+dL5Xv3X++mo80ec66E1KRimYq/viEEM/xM+e7vGMitdAoGAUAUe +pix+fa8615fFk0D37bJKIqZ8Uyg5pfLS9ZzZ/MYDG+14xuNOJSDbUMyNb0aYSfSf +PihqiIrbq9x6CTZJS2lwF4Oxcsmp4f0KX6BOxrM8PkKpQ08YVNL+GBYXTksHA6Y4 +XsbXVmWSj124l3lGfdm1w5cXQTQNPWVSz89FUvsCgYEArl27gbXndTLpZ4dNkeBS +0JMe84nPPrrwHbNwcqkoDiux5Ln+AZE8jYSlp4kUfd1A7XDTxXWXIxeD0YLiabf3 +5+/QzJ6j1qi77JoyadomnL7CFI5f2FotKt029PeVxAUOohao94p8J5OuaRMPvkGC +CNhjfRAIBhfm9kdyjPmwVLU= +-----END PRIVATE KEY----- diff --git a/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem new file mode 100644 index 0000000000000..b291aaa9362de --- /dev/null +++ b/x-pack/plugin/security/qa/microsoft-graph-authz-tests/src/javaRestTest/resources/server/cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRTCCAi2gAwIBAgIVAJpxxIbXWyvdd6/rIFXPgWe6fyvTMA0GCSqGSIb3DQEB +CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu +ZXJhdGVkIENBMB4XDTE4MTIyMTA3NDY1NVoXDTQ2MDUwNzA3NDY1NVowGTEXMBUG +A1UEAxMObGRhcC10ZXN0LWNhc2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCswbyZDtaghZXsPhs1+lqCnq5HmRT2P6Drrs9bJlABeql29IhzdHOGLr+l +TMhKOUpHuphgC31qbf/GvQLS65qdOzTjNfLv93+Jj0gp4S7Q6eRZvn1ihUgzECHa +zTYwIlzVs4sFPm5i2fQbDK7W6zQm+h9r6GjCYj01OeIAe7rbRI9Ar+svuHGfZnaQ +HzLZlfYkkM2bCaXBgKWVwmEUmwMW+IMOPCrVm+gk1MDbGnu9KtY/LqrJcddsqOdk +K8qJ0Lpchg3zlP4qIzbmWRyTUIy1USbcazjuC/vMmN4fr/Xr0Jrhi4Rw8l2LGdyA +8qnqtKYTqMzo3uv1ESlER8EAZDUbAgMBAAGjaTBnMB0GA1UdDgQWBBQaiCDScfBa +jHOSk04XOymffbLBxTAfBgNVHSMEGDAWgBROJaHRWe17um5rqqYn10aqedr55DAa +BgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwCQYDVR0TBAIwADANBgkqhkiG9w0B +AQsFAAOCAQEAXBovNqVg+VQ1LR0PfEMpbgbQlekky8qY2y1tz7J0ntGepAq+Np6n +7J9En6ty1ELZUvgPUCF2btQqZbv8uyHz/C+rojKC5xzHN5qbZ31o5/0I/kNase1Z +NbXuNJe3wAXuz+Mj5rtuOGZvlFsbtocuoydVYOclfqjUXcoZtqCcRamSvye7vGl2 +CHPqDi0uK8d75nE9Jrnmz/BNNV7CjPg636PJmCUrLL21+t69ZFL1eGAFtLBmmjcw +cMkyv9bJirjZbjt/9UB+fW9XzV3RVLAzfrIHtToupXmWc4+hTOnlbKfFwqB9fa7Y +XcCfGrZoJg9di1HbJrSJmv5QgRTM+/zkrA== +-----END CERTIFICATE----- diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index 1d3c3bf5f0a15..50a72ed4d0340 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -8,13 +8,10 @@ import com.unboundid.ldap.sdk.LDAPException; -import org.apache.logging.log4j.Logger; -import org.elasticsearch.ElasticsearchTimeoutException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.core.IOUtils; @@ -32,6 +29,7 @@ import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper.UserData; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.core.security.support.CancellableRunnable; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.security.authc.ldap.support.LdapLoadBalancing; @@ -46,10 +44,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -150,7 +146,7 @@ protected void doAuthenticate(UsernamePasswordToken token, ActionListener> cancellableLdapRunnable = new CancellableLdapRunnable<>( + final CancellableRunnable> cancellableLdapRunnable = new CancellableRunnable<>( listener, ex -> AuthenticationResult.unsuccessful("Authentication against realm [" + this.toString() + "] failed", ex), () -> sessionFactory.session( @@ -173,7 +169,7 @@ protected void doLookupUser(String username, ActionListener userActionList result -> userActionListener.onResponse(result.getValue()), userActionListener::onFailure ); - final CancellableLdapRunnable cancellableLdapRunnable = new CancellableLdapRunnable<>( + final CancellableRunnable cancellableLdapRunnable = new CancellableRunnable<>( userActionListener, e -> null, () -> sessionFactory.unauthenticatedSession( @@ -323,65 +319,5 @@ public void onFailure(Exception e) { } resultListener.onResponse(AuthenticationResult.unsuccessful(action + " failed", e)); } - - } - - /** - * A runnable that allows us to terminate and call the listener. We use this as a runnable can - * be queued and not executed for a long time or ever and this causes user requests to appear - * to hang. In these cases at least we can provide a response. - */ - static class CancellableLdapRunnable extends AbstractRunnable { - - private final Runnable in; - private final ActionListener listener; - private final Function defaultValue; - private final Logger logger; - private final AtomicReference state = new AtomicReference<>(LdapRunnableState.AWAITING_EXECUTION); - - CancellableLdapRunnable(ActionListener listener, Function defaultValue, Runnable in, Logger logger) { - this.listener = listener; - this.defaultValue = Objects.requireNonNull(defaultValue); - this.in = in; - this.logger = logger; - } - - @Override - public void onFailure(Exception e) { - logger.error("execution of ldap runnable failed", e); - final T result = defaultValue.apply(e); - listener.onResponse(result); - } - - @Override - protected void doRun() throws Exception { - if (state.compareAndSet(LdapRunnableState.AWAITING_EXECUTION, LdapRunnableState.EXECUTING)) { - in.run(); - } else { - logger.trace("skipping execution of ldap runnable as the current state is [{}]", state.get()); - } - } - - @Override - public void onRejection(Exception e) { - listener.onFailure(e); - } - - /** - * If the execution of this runnable has not already started, the runnable is cancelled and we pass an exception to the user - * listener - */ - void maybeTimeout() { - if (state.compareAndSet(LdapRunnableState.AWAITING_EXECUTION, LdapRunnableState.TIMED_OUT)) { - logger.warn("skipping execution of ldap runnable as it has been waiting for " + "execution too long"); - listener.onFailure(new ElasticsearchTimeoutException("timed out waiting for " + "execution of ldap runnable")); - } - } - - enum LdapRunnableState { - AWAITING_EXECUTION, - EXECUTING, - TIMED_OUT - } } } From cae28436394859a7824ca86158b9d0998489d227 Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Thu, 12 Jun 2025 10:44:53 +0100 Subject: [PATCH 084/102] Add `none` chunking strategy to disable automatic chunking for inference endpoints (#129150) This introduces a `none` chunking strategy that disables automatic chunking when using an inference endpoint. It enables users to provide pre-chunked input directly to a `semantic_text` field without any additional splitting. The chunking strategy can be configured either on the inference endpoint or directly in the `semantic_text` field definition. **Example:** ```json PUT test-index { "mappings": { "properties": { "my_semantic_field": { "type": "semantic_text", "chunking_settings": { "strategy": "none" <1> } } } } } ``` <1> Disables automatic chunking on `my_semantic_field`. ```json PUT test-index/_doc/1 { "my_semantic_field": ["my first chunk", "my second chunk", ...] <1> ... } ``` <1> Pre-chunked input provided as an array of strings. Each array element represents a single chunk that will be sent directly to the inference service without further processing. --- docs/changelog/129150.yaml | 6 + .../mapping-reference/semantic-text.md | 54 ++++++++- .../org/elasticsearch/TransportVersions.java | 2 + .../inference/ChunkingStrategy.java | 3 +- .../mock/AbstractTestInferenceService.java | 10 +- .../InferenceNamedWriteablesProvider.java | 4 + .../inference/chunking/ChunkerBuilder.java | 1 + .../chunking/ChunkingSettingsBuilder.java | 1 + .../chunking/NoneChunkingSettings.java | 110 ++++++++++++++++++ .../xpack/inference/chunking/NoopChunker.java | 38 ++++++ .../chunking/ChunkerBuilderTests.java | 9 +- .../chunking/ChunkingSettingsTests.java | 9 +- .../EmbeddingRequestChunkerTests.java | 16 +++ .../chunking/NoneChunkingSettingsTests.java | 35 ++++++ .../chunking/WordBoundaryChunkerTests.java | 7 +- .../mapper/SemanticTextFieldTests.java | 10 +- ...5_semantic_text_field_mapping_chunking.yml | 47 ++++++++ ...mantic_text_field_mapping_chunking_bwc.yml | 49 ++++++++ 18 files changed, 396 insertions(+), 15 deletions(-) create mode 100644 docs/changelog/129150.yaml create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettings.java create mode 100644 x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoopChunker.java create mode 100644 x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettingsTests.java diff --git a/docs/changelog/129150.yaml b/docs/changelog/129150.yaml new file mode 100644 index 0000000000000..5e53f6f6a9171 --- /dev/null +++ b/docs/changelog/129150.yaml @@ -0,0 +1,6 @@ +pr: 129150 +summary: Add `none` chunking strategy to disable automatic chunking for inference + endpoints +area: Machine Learning +type: feature +issues: [] diff --git a/docs/reference/elasticsearch/mapping-reference/semantic-text.md b/docs/reference/elasticsearch/mapping-reference/semantic-text.md index 47a3439ce5b94..2799c91d466ba 100644 --- a/docs/reference/elasticsearch/mapping-reference/semantic-text.md +++ b/docs/reference/elasticsearch/mapping-reference/semantic-text.md @@ -117,15 +117,16 @@ If specified, these will override the chunking settings set in the {{infer-cap}} endpoint associated with `inference_id`. If chunking settings are updated, they will not be applied to existing documents until they are reindexed. +To completely disable chunking, use the `none` chunking strategy. **Valid values for `chunking_settings`**: `type` - : Indicates the type of chunking strategy to use. Valid values are `word` or + : Indicates the type of chunking strategy to use. Valid values are `none`, `word` or `sentence`. Required. `max_chunk_size` - : The maximum number of works in a chunk. Required. + : The maximum number of words in a chunk. Required for `word` and `sentence` strategies. `overlap` : The number of overlapping words allowed in chunks. This cannot be defined as @@ -136,6 +137,12 @@ until they are reindexed. : The number of overlapping sentences allowed in chunks. Valid values are `0` or `1`. Required for `sentence` type chunking settings +::::{warning} +If the input exceeds the maximum token limit of the underlying model, some services (such as OpenAI) may return an +error. In contrast, the `elastic` and `elasticsearch` services will automatically truncate the input to fit within the +model's limit. +:::: + ## {{infer-cap}} endpoint validation [infer-endpoint-validation] The `inference_id` will not be validated when the mapping is created, but when @@ -166,10 +173,49 @@ For more details on chunking and how to configure chunking settings, see [Configuring chunking](https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference) in the Inference API documentation. +You can pre-chunk the input by sending it to Elasticsearch as an array of strings. +Example: + +```console +PUT test-index +{ + "mappings": { + "properties": { + "my_semantic_field": { + "type": "semantic_text", + "chunking_settings": { + "strategy": "none" <1> + } + } + } + } +} +``` + +1. Disable chunking on `my_semantic_field`. + +```console +PUT test-index/_doc/1 +{ + "my_semantic_field": ["my first chunk", "my second chunk", ...] <1> + ... +} +``` + +1. The text is pre-chunked and provided as an array of strings. + Each element in the array represents a single chunk that will be sent directly to the inference service without further chunking. + +**Important considerations**: + +* When providing pre-chunked input, ensure that you set the chunking strategy to `none` to avoid additional processing. +* Each chunk should be sized carefully, staying within the token limit of the inference service and the underlying model. +* If a chunk exceeds the model's token limit, the behavior depends on the service: + * Some services (such as OpenAI) will return an error. + * Others (such as `elastic` and `elasticsearch`) will automatically truncate the input. + Refer to [this tutorial](docs-content://solutions/search/semantic-search/semantic-search-semantic-text.md) -to learn more about semantic search using `semantic_text` and the `semantic` -query. +to learn more about semantic search using `semantic_text`. ## Extracting Relevant Fragments from Semantic Text [semantic-text-highlighting] diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 6e942cdc263e7..d008b885a40a8 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -194,6 +194,7 @@ static TransportVersion def(int id) { public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM_8_19 = def(8_841_0_46); public static final TransportVersion ML_INFERENCE_MISTRAL_CHAT_COMPLETION_ADDED_8_19 = def(8_841_0_47); public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK_ADDED_8_19 = def(8_841_0_48); + public static final TransportVersion NONE_CHUNKING_STRATEGY_8_19 = def(8_841_0_49); public static final TransportVersion V_9_0_0 = def(9_000_0_09); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_1 = def(9_000_0_10); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_2 = def(9_000_0_11); @@ -294,6 +295,7 @@ static TransportVersion def(int id) { public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK = def(9_094_0_00); public static final TransportVersion SEARCH_LOAD_PER_INDEX_STATS = def(9_095_0_00); public static final TransportVersion HEAP_USAGE_IN_CLUSTER_INFO = def(9_096_0_00); + public static final TransportVersion NONE_CHUNKING_STRATEGY = def(9_097_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/inference/ChunkingStrategy.java b/server/src/main/java/org/elasticsearch/inference/ChunkingStrategy.java index bb5e0254834a3..78404c1b409ee 100644 --- a/server/src/main/java/org/elasticsearch/inference/ChunkingStrategy.java +++ b/server/src/main/java/org/elasticsearch/inference/ChunkingStrategy.java @@ -15,7 +15,8 @@ public enum ChunkingStrategy { WORD("word"), - SENTENCE("sentence"); + SENTENCE("sentence"), + NONE("none"); private final String chunkingStrategy; diff --git a/x-pack/plugin/inference/qa/test-service-plugin/src/main/java/org/elasticsearch/xpack/inference/mock/AbstractTestInferenceService.java b/x-pack/plugin/inference/qa/test-service-plugin/src/main/java/org/elasticsearch/xpack/inference/mock/AbstractTestInferenceService.java index 34e2af8034527..44c9d0463cd05 100644 --- a/x-pack/plugin/inference/qa/test-service-plugin/src/main/java/org/elasticsearch/xpack/inference/mock/AbstractTestInferenceService.java +++ b/x-pack/plugin/inference/qa/test-service-plugin/src/main/java/org/elasticsearch/xpack/inference/mock/AbstractTestInferenceService.java @@ -25,6 +25,7 @@ import org.elasticsearch.inference.TaskSettings; import org.elasticsearch.inference.TaskType; import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.chunking.NoopChunker; import org.elasticsearch.xpack.inference.chunking.WordBoundaryChunker; import org.elasticsearch.xpack.inference.chunking.WordBoundaryChunkingSettings; @@ -126,7 +127,14 @@ protected List chunkInputs(ChunkInferenceInput input) { } List chunkedInputs = new ArrayList<>(); - if (chunkingSettings.getChunkingStrategy() == ChunkingStrategy.WORD) { + if (chunkingSettings.getChunkingStrategy() == ChunkingStrategy.NONE) { + var offsets = NoopChunker.INSTANCE.chunk(input.input(), chunkingSettings); + List ret = new ArrayList<>(); + for (var offset : offsets) { + ret.add(new ChunkedInput(inputText.substring(offset.start(), offset.end()), offset.start(), offset.end())); + } + return ret; + } else if (chunkingSettings.getChunkingStrategy() == ChunkingStrategy.WORD) { WordBoundaryChunker chunker = new WordBoundaryChunker(); WordBoundaryChunkingSettings wordBoundaryChunkingSettings = (WordBoundaryChunkingSettings) chunkingSettings; List offsets = chunker.chunk( diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java index 3fd5b06450a73..a495f2af93eb2 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/InferenceNamedWriteablesProvider.java @@ -26,6 +26,7 @@ import org.elasticsearch.xpack.core.inference.results.TextEmbeddingByteResults; import org.elasticsearch.xpack.core.inference.results.TextEmbeddingFloatResults; import org.elasticsearch.xpack.inference.action.task.StreamingTaskManager; +import org.elasticsearch.xpack.inference.chunking.NoneChunkingSettings; import org.elasticsearch.xpack.inference.chunking.SentenceBoundaryChunkingSettings; import org.elasticsearch.xpack.inference.chunking.WordBoundaryChunkingSettings; import org.elasticsearch.xpack.inference.common.amazon.AwsSecretSettings; @@ -553,6 +554,9 @@ private static void addInternalNamedWriteables(List namedWriteables) { + namedWriteables.add( + new NamedWriteableRegistry.Entry(ChunkingSettings.class, NoneChunkingSettings.NAME, in -> NoneChunkingSettings.INSTANCE) + ); namedWriteables.add( new NamedWriteableRegistry.Entry(ChunkingSettings.class, WordBoundaryChunkingSettings.NAME, WordBoundaryChunkingSettings::new) ); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilder.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilder.java index 830f1579348f6..f10748c2fec97 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilder.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilder.java @@ -16,6 +16,7 @@ public static Chunker fromChunkingStrategy(ChunkingStrategy chunkingStrategy) { } return switch (chunkingStrategy) { + case NONE -> NoopChunker.INSTANCE; case WORD -> new WordBoundaryChunker(); case SENTENCE -> new SentenceBoundaryChunker(); }; diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsBuilder.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsBuilder.java index 25553a4c760f0..b1bc5987eaa99 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsBuilder.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsBuilder.java @@ -45,6 +45,7 @@ public static ChunkingSettings fromMap(Map settings, boolean ret settings.get(ChunkingSettingsOptions.STRATEGY.toString()).toString() ); return switch (chunkingStrategy) { + case NONE -> NoneChunkingSettings.INSTANCE; case WORD -> WordBoundaryChunkingSettings.fromMap(new HashMap<>(settings)); case SENTENCE -> SentenceBoundaryChunkingSettings.fromMap(new HashMap<>(settings)); }; diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettings.java new file mode 100644 index 0000000000000..bd3da61d81063 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettings.java @@ -0,0 +1,110 @@ +/* + * 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.inference.chunking; + +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.inference.ChunkingSettings; +import org.elasticsearch.inference.ChunkingStrategy; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class NoneChunkingSettings implements ChunkingSettings { + public static final String NAME = "NoneChunkingSettings"; + public static NoneChunkingSettings INSTANCE = new NoneChunkingSettings(); + + private static final ChunkingStrategy STRATEGY = ChunkingStrategy.NONE; + private static final Set VALID_KEYS = Set.of(ChunkingSettingsOptions.STRATEGY.toString()); + + private NoneChunkingSettings() {} + + @Override + public ChunkingStrategy getChunkingStrategy() { + return STRATEGY; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + throw new IllegalStateException("not used"); + } + + @Override + public boolean supportsVersion(TransportVersion version) { + return version.isPatchFrom(TransportVersions.NONE_CHUNKING_STRATEGY_8_19) + || version.onOrAfter(TransportVersions.NONE_CHUNKING_STRATEGY); + } + + @Override + public void writeTo(StreamOutput out) throws IOException {} + + @Override + public Map asMap() { + return Map.of(ChunkingSettingsOptions.STRATEGY.toString(), STRATEGY.toString().toLowerCase(Locale.ROOT)); + } + + public static NoneChunkingSettings fromMap(Map map) { + ValidationException validationException = new ValidationException(); + + var invalidSettings = map.keySet().stream().filter(key -> VALID_KEYS.contains(key) == false).toArray(); + if (invalidSettings.length > 0) { + validationException.addValidationError( + Strings.format( + "When chunking is disabled (none), settings can not have the following: %s", + Arrays.toString(invalidSettings) + ) + ); + } + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return NoneChunkingSettings.INSTANCE; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + { + builder.field(ChunkingSettingsOptions.STRATEGY.toString(), STRATEGY); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return true; + } + + @Override + public int hashCode() { + return Objects.hash(getClass()); + } + + @Override + public String toString() { + return Strings.toString(this); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoopChunker.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoopChunker.java new file mode 100644 index 0000000000000..4698275de7c59 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/chunking/NoopChunker.java @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.chunking; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.inference.ChunkingSettings; +import org.elasticsearch.xpack.inference.services.openai.embeddings.OpenAiEmbeddingsModel; + +import java.util.List; + +/** + * A {@link Chunker} implementation that returns the input unchanged (no chunking is performed). + * + *

WARNINGIf the input exceeds the maximum token limit, some services (such as {@link OpenAiEmbeddingsModel}) + * may return an error. + *

+ */ +public class NoopChunker implements Chunker { + public static final NoopChunker INSTANCE = new NoopChunker(); + + private NoopChunker() {} + + @Override + public List chunk(String input, ChunkingSettings chunkingSettings) { + if (chunkingSettings instanceof NoneChunkingSettings) { + return List.of(new ChunkOffset(0, input.length())); + } else { + throw new IllegalArgumentException( + Strings.format("NoopChunker can't use ChunkingSettings with strategy [%s]", chunkingSettings.getChunkingStrategy()) + ); + } + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilderTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilderTests.java index d2aea45d4603c..471b5400991e2 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilderTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkerBuilderTests.java @@ -27,6 +27,13 @@ public void testValidChunkingStrategy() { } private Map> chunkingStrategyToExpectedChunkerClassMap() { - return Map.of(ChunkingStrategy.WORD, WordBoundaryChunker.class, ChunkingStrategy.SENTENCE, SentenceBoundaryChunker.class); + return Map.of( + ChunkingStrategy.NONE, + NoopChunker.class, + ChunkingStrategy.WORD, + WordBoundaryChunker.class, + ChunkingStrategy.SENTENCE, + SentenceBoundaryChunker.class + ); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsTests.java index 2832c2f64e0e6..9dfa417c3c477 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/ChunkingSettingsTests.java @@ -20,6 +20,9 @@ public static ChunkingSettings createRandomChunkingSettings() { ChunkingStrategy randomStrategy = randomFrom(ChunkingStrategy.values()); switch (randomStrategy) { + case NONE -> { + return NoneChunkingSettings.INSTANCE; + } case WORD -> { var maxChunkSize = randomIntBetween(10, 300); return new WordBoundaryChunkingSettings(maxChunkSize, randomIntBetween(1, maxChunkSize / 2)); @@ -37,15 +40,15 @@ public static Map createRandomChunkingSettingsMap() { chunkingSettingsMap.put(ChunkingSettingsOptions.STRATEGY.toString(), randomStrategy.toString()); switch (randomStrategy) { + case NONE -> { + } case WORD -> { var maxChunkSize = randomIntBetween(10, 300); chunkingSettingsMap.put(ChunkingSettingsOptions.MAX_CHUNK_SIZE.toString(), maxChunkSize); chunkingSettingsMap.put(ChunkingSettingsOptions.OVERLAP.toString(), randomIntBetween(1, maxChunkSize / 2)); } - case SENTENCE -> { - chunkingSettingsMap.put(ChunkingSettingsOptions.MAX_CHUNK_SIZE.toString(), randomIntBetween(20, 300)); - } + case SENTENCE -> chunkingSettingsMap.put(ChunkingSettingsOptions.MAX_CHUNK_SIZE.toString(), randomIntBetween(20, 300)); default -> { } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/EmbeddingRequestChunkerTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/EmbeddingRequestChunkerTests.java index a9cf741815d51..411d992adfa3d 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/EmbeddingRequestChunkerTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/EmbeddingRequestChunkerTests.java @@ -46,6 +46,22 @@ public void testEmptyInput_SentenceChunker() { assertThat(batches, empty()); } + public void testEmptyInput_NoopChunker() { + var batches = new EmbeddingRequestChunker<>(List.of(), 10, NoneChunkingSettings.INSTANCE).batchRequestsWithListeners( + testListener() + ); + assertThat(batches, empty()); + } + + public void testAnyInput_NoopChunker() { + var randomInput = randomAlphaOfLengthBetween(100, 1000); + var batches = new EmbeddingRequestChunker<>(List.of(new ChunkInferenceInput(randomInput)), 10, NoneChunkingSettings.INSTANCE) + .batchRequestsWithListeners(testListener()); + assertThat(batches, hasSize(1)); + assertThat(batches.get(0).batch().inputs().get(), hasSize(1)); + assertThat(batches.get(0).batch().inputs().get().get(0), Matchers.is(randomInput)); + } + public void testWhitespaceInput_SentenceChunker() { var batches = new EmbeddingRequestChunker<>( List.of(new ChunkInferenceInput(" ")), diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettingsTests.java new file mode 100644 index 0000000000000..660b223d0cab0 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/NoneChunkingSettingsTests.java @@ -0,0 +1,35 @@ +/* + * 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.inference.chunking; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.test.AbstractWireSerializingTestCase; + +import java.io.IOException; + +public class NoneChunkingSettingsTests extends AbstractWireSerializingTestCase { + @Override + protected Writeable.Reader instanceReader() { + return in -> NoneChunkingSettings.INSTANCE; + } + + @Override + protected NoneChunkingSettings createTestInstance() { + return NoneChunkingSettings.INSTANCE; + } + + @Override + protected boolean shouldBeSame(NoneChunkingSettings newInstance) { + return true; + } + + @Override + protected NoneChunkingSettings mutateInstance(NoneChunkingSettings instance) throws IOException { + return null; + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/WordBoundaryChunkerTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/WordBoundaryChunkerTests.java index a4228d50eae4b..65d9645daca4f 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/WordBoundaryChunkerTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/chunking/WordBoundaryChunkerTests.java @@ -147,8 +147,11 @@ public void testNumberOfChunksWithWordBoundaryChunkingSettings() { } public void testInvalidChunkingSettingsProvided() { - ChunkingSettings chunkingSettings = new SentenceBoundaryChunkingSettings(randomIntBetween(20, 300), 0); - assertThrows(IllegalArgumentException.class, () -> { new WordBoundaryChunker().chunk(TEST_TEXT, chunkingSettings); }); + ChunkingSettings chunkingSettings1 = new SentenceBoundaryChunkingSettings(randomIntBetween(20, 300), 0); + assertThrows(IllegalArgumentException.class, () -> { new WordBoundaryChunker().chunk(TEST_TEXT, chunkingSettings1); }); + + ChunkingSettings chunkingSettings2 = NoneChunkingSettings.INSTANCE; + assertThrows(IllegalArgumentException.class, () -> { new WordBoundaryChunker().chunk(TEST_TEXT, chunkingSettings2); }); } public void testWindowSpanningWithOverlapNumWordsInOverlapSection() { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldTests.java index 6b2f9d387071b..d1499f4009d0a 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldTests.java @@ -30,6 +30,7 @@ import org.elasticsearch.xpack.core.inference.results.TextEmbeddingByteResults; import org.elasticsearch.xpack.core.inference.results.TextEmbeddingFloatResults; import org.elasticsearch.xpack.core.utils.FloatConversionUtils; +import org.elasticsearch.xpack.inference.chunking.NoneChunkingSettings; import org.elasticsearch.xpack.inference.chunking.SentenceBoundaryChunkingSettings; import org.elasticsearch.xpack.inference.chunking.WordBoundaryChunkingSettings; import org.elasticsearch.xpack.inference.model.TestModel; @@ -342,9 +343,12 @@ public static ChunkingSettings generateRandomChunkingSettings(boolean allowNull) if (allowNull && randomBoolean()) { return null; // Use model defaults } - return randomBoolean() - ? new WordBoundaryChunkingSettings(randomIntBetween(20, 100), randomIntBetween(1, 10)) - : new SentenceBoundaryChunkingSettings(randomIntBetween(20, 100), randomIntBetween(0, 1)); + return switch (randomIntBetween(0, 2)) { + case 0 -> NoneChunkingSettings.INSTANCE; + case 1 -> new WordBoundaryChunkingSettings(randomIntBetween(20, 100), randomIntBetween(1, 10)); + case 2 -> new SentenceBoundaryChunkingSettings(randomIntBetween(20, 100), randomIntBetween(0, 1)); + default -> throw new IllegalStateException("Illegal state while generating random chunking settings"); + }; } public static ChunkingSettings generateRandomChunkingSettingsOtherThan(ChunkingSettings chunkingSettings) { diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking.yml index a6ff307f0ef4a..f3167e69e113f 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking.yml @@ -91,6 +91,20 @@ setup: max_chunk_size: 10 overlap: 1 + - do: + indices.create: + index: none-chunking-dense + body: + mappings: + properties: + keyword_field: + type: keyword + inference_field: + type: semantic_text + inference_id: dense-inference-id + chunking_settings: + strategy: none + - do: index: index: default-chunking-sparse @@ -127,6 +141,15 @@ setup: inference_field: "Elasticsearch is an open source, distributed, RESTful, search engine which is built on top of Lucene internally and enjoys all the features it provides." refresh: true + - do: + index: + index: none-chunking-dense + id: doc_5 + body: + keyword_field: "none chunking" + inference_field: "Elasticsearch is an open source, distributed, RESTful, search engine which is built on top of Lucene internally and enjoys all the features it provides." + refresh: true + --- "We return chunking configurations with mappings": @@ -158,6 +181,11 @@ setup: - match: { "custom-chunking-dense.mappings.properties.inference_field.chunking_settings.max_chunk_size": 10 } - match: { "custom-chunking-dense.mappings.properties.inference_field.chunking_settings.overlap": 1 } + - do: + indices.get_mapping: + index: none-chunking-dense + + - match: { "none-chunking-dense.mappings.properties.inference_field.chunking_settings.strategy": "none" } --- "We do not set custom chunking settings for null or empty specified chunking settings": @@ -283,6 +311,25 @@ setup: - match: { hits.hits.0.highlight.inference_field.1: " which is built on top of Lucene internally and enjoys" } - match: { hits.hits.0.highlight.inference_field.2: " enjoys all the features it provides." } + - do: + search: + index: none-chunking-dense + body: + query: + semantic: + field: "inference_field" + query: "What is Elasticsearch?" + highlight: + fields: + inference_field: + type: "semantic" + number_of_fragments: 2 + + - match: { hits.total.value: 1 } + - match: { hits.hits.0._id: "doc_5" } + - length: { hits.hits.0.highlight.inference_field: 1 } + - match: { hits.hits.0.highlight.inference_field.0: "Elasticsearch is an open source, distributed, RESTful, search engine which is built on top of Lucene internally and enjoys all the features it provides." } + --- "We respect multiple semantic_text fields with different chunking configurations": diff --git a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking_bwc.yml b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking_bwc.yml index f189d5535bb77..9bf49741bca64 100644 --- a/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking_bwc.yml +++ b/x-pack/plugin/inference/src/yamlRestTest/resources/rest-api-spec/test/inference/25_semantic_text_field_mapping_chunking_bwc.yml @@ -99,6 +99,22 @@ setup: max_chunk_size: 10 overlap: 1 + - do: + indices.create: + index: none-chunking-dense + body: + settings: + index.mapping.semantic_text.use_legacy_format: true + mappings: + properties: + keyword_field: + type: keyword + inference_field: + type: semantic_text + inference_id: dense-inference-id + chunking_settings: + strategy: none + - do: index: index: default-chunking-sparse @@ -135,6 +151,15 @@ setup: inference_field: "Elasticsearch is an open source, distributed, RESTful, search engine which is built on top of Lucene internally and enjoys all the features it provides." refresh: true + - do: + index: + index: none-chunking-dense + id: doc_5 + body: + keyword_field: "none chunking" + inference_field: "Elasticsearch is an open source, distributed, RESTful, search engine which is built on top of Lucene internally and enjoys all the features it provides." + refresh: true + --- "We return chunking configurations with mappings": @@ -166,6 +191,11 @@ setup: - match: { "custom-chunking-dense.mappings.properties.inference_field.chunking_settings.max_chunk_size": 10 } - match: { "custom-chunking-dense.mappings.properties.inference_field.chunking_settings.overlap": 1 } + - do: + indices.get_mapping: + index: none-chunking-dense + + - match: { "none-chunking-dense.mappings.properties.inference_field.chunking_settings.strategy": "none" } --- "We do not set custom chunking settings for null or empty specified chunking settings": @@ -295,6 +325,25 @@ setup: - match: { hits.hits.0.highlight.inference_field.1: " which is built on top of Lucene internally and enjoys" } - match: { hits.hits.0.highlight.inference_field.2: " enjoys all the features it provides." } + - do: + search: + index: none-chunking-dense + body: + query: + semantic: + field: "inference_field" + query: "What is Elasticsearch?" + highlight: + fields: + inference_field: + type: "semantic" + number_of_fragments: 2 + + - match: { hits.total.value: 1 } + - match: { hits.hits.0._id: "doc_5" } + - length: { hits.hits.0.highlight.inference_field: 1 } + - match: { hits.hits.0.highlight.inference_field.0: "Elasticsearch is an open source, distributed, RESTful, search engine which is built on top of Lucene internally and enjoys all the features it provides." } + --- "We respect multiple semantic_text fields with different chunking configurations": From 6852dedf02546b84668b71df4d7d050500efcd63 Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 12 Jun 2025 11:56:09 +0200 Subject: [PATCH 085/102] Fix broken bwc logic in text field mapper introduced by #129126 (#129308) A missing condition in the bwc logic caused a text field to be a stored, while before #129126, this wasn't the case. --- .../index/mapper/TextFieldMapper.java | 2 +- .../index/mapper/TextFieldMapperTests.java | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java index 0e49506cd92e7..ea4fd45ab3df4 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java @@ -316,7 +316,7 @@ public Builder( && this.withinMultiField == false && multiFieldsBuilder.hasSyntheticSourceCompatibleKeywordField() == false; } else { - return isSyntheticSourceEnabled; + return isSyntheticSourceEnabled && multiFieldsBuilder.hasSyntheticSourceCompatibleKeywordField() == false; } }); this.indexCreatedVersion = indexCreatedVersion; diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java index fa2fce306ff8a..2c8dc48cf0484 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TextFieldMapperTests.java @@ -51,6 +51,7 @@ import org.elasticsearch.index.IndexMode; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.index.IndexVersions; import org.elasticsearch.index.analysis.AnalyzerScope; import org.elasticsearch.index.analysis.CharFilterFactory; import org.elasticsearch.index.analysis.CustomAnalyzer; @@ -350,6 +351,31 @@ public void testStoreParameterDefaultsSyntheticSourceWithKeywordMultiField() thr assertThat(fieldType.stored(), is(false)); } + public void testStoreParameterDefaultsSyntheticSourceWithKeywordMultiFieldBwc() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "text"); + b.startObject("fields"); + b.startObject("keyword"); + b.field("type", "keyword"); + b.endObject(); + b.endObject(); + b.endObject(); + }); + IndexVersion beforeVersion = IndexVersions.INDEX_INT_SORT_INT_TYPE; + DocumentMapper mapper = createMapperService(beforeVersion, indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + List fields = doc.rootDoc().getFields("name"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(false)); + } + public void testStoreParameterDefaultsSyntheticSourceTextFieldIsMultiField() throws IOException { var indexSettingsBuilder = getIndexSettingsBuilder(); indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); @@ -374,6 +400,31 @@ public void testStoreParameterDefaultsSyntheticSourceTextFieldIsMultiField() thr assertThat(fieldType.stored(), is(false)); } + public void testStoreParameterDefaultsSyntheticSourceTextFieldIsMultiFieldBwc() throws IOException { + var indexSettingsBuilder = getIndexSettingsBuilder(); + indexSettingsBuilder.put(IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic"); + var indexSettings = indexSettingsBuilder.build(); + + var mapping = mapping(b -> { + b.startObject("name"); + b.field("type", "keyword"); + b.startObject("fields"); + b.startObject("text"); + b.field("type", "text"); + b.endObject(); + b.endObject(); + b.endObject(); + }); + IndexVersion beforeVersion = IndexVersions.INDEX_INT_SORT_INT_TYPE; + DocumentMapper mapper = createMapperService(beforeVersion, indexSettings, mapping).documentMapper(); + + var source = source(b -> b.field("name", "quick brown fox")); + ParsedDocument doc = mapper.parse(source); + List fields = doc.rootDoc().getFields("name.text"); + IndexableFieldType fieldType = fields.get(0).fieldType(); + assertThat(fieldType.stored(), is(true)); + } + public void testBWCSerialization() throws IOException { MapperService mapperService = createMapperService(fieldMapping(b -> { b.field("type", "text"); From 3f1fba046990c72887e3c0db8bedbc05a0cbc8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Thu, 12 Jun 2025 13:02:35 +0200 Subject: [PATCH 086/102] [ESQL] Fix SpatialDocValuesExtraction rule replacing TimeSeries agg node (#129273) `TimeSeriesAggregateExec` (TS) node inherits from `AggregateExec` (STATS). The `SpatialDocValuesExtraction` rule was replacing all `AggregateExec` instances with another `AggregateExec`, whether the same class or not. --- .../local/SpatialDocValuesExtraction.java | 10 +--------- .../xpack/esql/plan/physical/AggregateExec.java | 4 ++++ .../plan/physical/TimeSeriesAggregateExec.java | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java index d55c0f9f17384..adcc8bc62acb4 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/SpatialDocValuesExtraction.java @@ -97,15 +97,7 @@ && allowedForDocValues(fieldAttribute, ctx.searchStats(), agg, foundAttributes)) } } if (changedAggregates) { - exec = new AggregateExec( - agg.source(), - agg.child(), - agg.groupings(), - orderedAggregates, - agg.getMode(), - agg.intermediateAttributes(), - agg.estimatedRowSize() - ); + exec = agg.withAggregates(orderedAggregates); } } if (exec instanceof EvalExec evalExec) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/AggregateExec.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/AggregateExec.java index 19b036184d487..6d0991a24a36c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/AggregateExec.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/AggregateExec.java @@ -120,6 +120,10 @@ public List aggregates() { return aggregates; } + public AggregateExec withAggregates(List newAggregates) { + return new AggregateExec(source(), child(), groupings, newAggregates, mode, intermediateAttributes, estimatedRowSize); + } + public AggregateExec withMode(AggregatorMode newMode) { return new AggregateExec(source(), child(), groupings, aggregates, newMode, intermediateAttributes, estimatedRowSize); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/TimeSeriesAggregateExec.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/TimeSeriesAggregateExec.java index e1c220b5f623b..23c4303fb6e0d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/TimeSeriesAggregateExec.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/TimeSeriesAggregateExec.java @@ -97,6 +97,21 @@ public TimeSeriesAggregateExec replaceChild(PhysicalPlan newChild) { ); } + @Override + public AggregateExec withAggregates(List newAggregates) { + return new TimeSeriesAggregateExec( + source(), + child(), + groupings(), + newAggregates, + getMode(), + intermediateAttributes(), + estimatedRowSize(), + timeBucket + ); + } + + @Override public TimeSeriesAggregateExec withMode(AggregatorMode newMode) { return new TimeSeriesAggregateExec( source(), From 3fc3440d54013940c3ef7a83ad2bde9e612cbd1b Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:42:38 +0200 Subject: [PATCH 087/102] Make `TransportMoveToStepAction` project-aware (#129252) Future work is necessary to make the YAML tests pass in MP mode. --- .../xpack/ilm/IndexLifecycleService.java | 6 +++--- .../xpack/ilm/PolicyStepsRegistry.java | 18 +++++++--------- .../ilm/action/TransportMoveToStepAction.java | 21 ++++++++++++------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java index 3676bbf57afaf..7742eb70ca918 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleService.java @@ -138,12 +138,12 @@ public void maybeRunAsyncAction(ClusterState clusterState, IndexMetadata indexMe * Resolve the given phase, action, and name into a real {@link StepKey}. The phase is always * required, but the action and name are optional. If a name is specified, an action is also required. */ - public StepKey resolveStepKey(ClusterState state, Index index, String phase, @Nullable String action, @Nullable String name) { + public StepKey resolveStepKey(ProjectMetadata project, Index index, String phase, @Nullable String action, @Nullable String name) { if (name == null) { if (action == null) { - return this.policyRegistry.getFirstStepForPhase(state, index, phase); + return this.policyRegistry.getFirstStepForPhase(project, index, phase); } else { - return this.policyRegistry.getFirstStepForPhaseAndAction(state, index, phase, action); + return this.policyRegistry.getFirstStepForPhaseAndAction(project, index, phase, action); } } else { assert action != null diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/PolicyStepsRegistry.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/PolicyStepsRegistry.java index d3e9998c99907..7bbf2f2563c5a 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/PolicyStepsRegistry.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/PolicyStepsRegistry.java @@ -10,10 +10,9 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.client.internal.Client; -import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.DiffableUtils; import org.elasticsearch.cluster.metadata.IndexMetadata; -import org.elasticsearch.cluster.metadata.Metadata; +import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -201,12 +200,11 @@ public void clear() { * Return all ordered steps for the current policy for the index. Does not * resolve steps using the phase caching, but only for the currently existing policy. */ - private List getAllStepsForIndex(ClusterState state, Index index) { - final Metadata metadata = state.metadata(); - if (metadata.getProject().hasIndex(index) == false) { + private List getAllStepsForIndex(ProjectMetadata project, Index index) { + if (project.hasIndex(index) == false) { throw new IllegalArgumentException("index " + index + " does not exist in the current cluster state"); } - final IndexMetadata indexMetadata = metadata.getProject().index(index); + final IndexMetadata indexMetadata = project.index(index); final String policyName = indexMetadata.getLifecyclePolicyName(); final LifecyclePolicyMetadata policyMetadata = lifecyclePolicyMap.get(policyName); if (policyMetadata == null) { @@ -225,8 +223,8 @@ private List getAllStepsForIndex(ClusterState state, Index index) { * first step in that phase, if it exists, or null otherwise. */ @Nullable - public Step.StepKey getFirstStepForPhase(ClusterState state, Index index, String phase) { - return getAllStepsForIndex(state, index).stream() + public Step.StepKey getFirstStepForPhase(ProjectMetadata project, Index index, String phase) { + return getAllStepsForIndex(project, index).stream() .map(Step::getKey) .filter(stepKey -> phase.equals(stepKey.phase())) .findFirst() @@ -238,8 +236,8 @@ public Step.StepKey getFirstStepForPhase(ClusterState state, Index index, String * for the first step in that phase, if it exists, or null otherwise. */ @Nullable - public Step.StepKey getFirstStepForPhaseAndAction(ClusterState state, Index index, String phase, String action) { - return getAllStepsForIndex(state, index).stream() + public Step.StepKey getFirstStepForPhaseAndAction(ProjectMetadata project, Index index, String phase, String action) { + return getAllStepsForIndex(project, index).stream() .map(Step::getKey) .filter(stepKey -> phase.equals(stepKey.phase())) .filter(stepKey -> action.equals(stepKey.action())) diff --git a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportMoveToStepAction.java b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportMoveToStepAction.java index 5a05a4cc6283b..de0dcbd143263 100644 --- a/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportMoveToStepAction.java +++ b/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/action/TransportMoveToStepAction.java @@ -22,6 +22,7 @@ import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.cluster.project.ProjectResolver; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; @@ -50,7 +51,8 @@ public class TransportMoveToStepAction extends TransportMasterNodeAction { private static final Logger logger = LogManager.getLogger(TransportMoveToStepAction.class); - IndexLifecycleService indexLifecycleService; + private final IndexLifecycleService indexLifecycleService; + private final ProjectResolver projectResolver; @Inject public TransportMoveToStepAction( @@ -58,7 +60,8 @@ public TransportMoveToStepAction( ClusterService clusterService, ThreadPool threadPool, ActionFilters actionFilters, - IndexLifecycleService indexLifecycleService + IndexLifecycleService indexLifecycleService, + ProjectResolver projectResolver ) { super( ILMActions.MOVE_TO_STEP.name(), @@ -71,11 +74,13 @@ public TransportMoveToStepAction( EsExecutors.DIRECT_EXECUTOR_SERVICE ); this.indexLifecycleService = indexLifecycleService; + this.projectResolver = projectResolver; } @Override protected void masterOperation(Task task, Request request, ClusterState state, ActionListener listener) { - IndexMetadata indexMetadata = state.metadata().getProject().index(request.getIndex()); + final var project = projectResolver.getProjectMetadata(state); + IndexMetadata indexMetadata = project.index(request.getIndex()); if (indexMetadata == null) { listener.onFailure(new IllegalArgumentException("index [" + request.getIndex() + "] does not exist")); return; @@ -95,7 +100,7 @@ protected void masterOperation(Task task, Request request, ClusterState state, A // Resolve the key that could have optional parts into one // that is totally concrete given the existing policy and index Step.StepKey concreteTargetStepKey = indexLifecycleService.resolveStepKey( - state, + project, indexMetadata.getIndex(), abstractTargetKey.getPhase(), abstractTargetKey.getAction(), @@ -125,8 +130,9 @@ protected void masterOperation(Task task, Request request, ClusterState state, A public ClusterState execute(ClusterState currentState) { // Resolve the key that could have optional parts into one // that is totally concrete given the existing policy and index + final var currentProject = currentState.metadata().getProject(project.id()); Step.StepKey concreteTargetStepKey = indexLifecycleService.resolveStepKey( - state, + currentProject, indexMetadata.getIndex(), abstractTargetKey.getPhase(), abstractTargetKey.getAction(), @@ -148,7 +154,7 @@ public ClusterState execute(ClusterState currentState) { concreteTargetKey.set(concreteTargetStepKey); final var updatedProject = indexLifecycleService.moveIndexToStep( - currentState.metadata().getProject(), + currentProject, indexMetadata.getIndex(), request.getCurrentStepKey(), concreteTargetKey.get() @@ -158,7 +164,8 @@ public ClusterState execute(ClusterState currentState) { @Override public void clusterStateProcessed(ClusterState oldState, ClusterState newState) { - IndexMetadata newIndexMetadata = newState.metadata().getProject().index(indexMetadata.getIndex()); + final var newProjectState = newState.projectState(project.id()); + IndexMetadata newIndexMetadata = newProjectState.metadata().index(indexMetadata.getIndex()); if (newIndexMetadata == null) { // The index has somehow been deleted - there shouldn't be any opportunity for this to happen, but just in case. logger.debug( From d1391d5d1cc536143001cfa8a203bc34a51e34a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Thu, 12 Jun 2025 14:20:54 +0200 Subject: [PATCH 088/102] [DOCS] Adds term vectors API examples (#129328) * [DOCS] Adds term vectors API examples. * Update docs/reference/elasticsearch/rest-apis/term-vectors-examples.md Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> * [DOCS] Addresses feedback. * [DOCS] Fixes link. --------- Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- .../mapping-reference/term-vector.md | 2 + .../rest-apis/term-vectors-examples.md | 307 ++++++++++++++++++ docs/reference/elasticsearch/toc.yml | 27 +- 3 files changed, 323 insertions(+), 13 deletions(-) create mode 100644 docs/reference/elasticsearch/rest-apis/term-vectors-examples.md diff --git a/docs/reference/elasticsearch/mapping-reference/term-vector.md b/docs/reference/elasticsearch/mapping-reference/term-vector.md index 3244e131cde11..b854e0ff07538 100644 --- a/docs/reference/elasticsearch/mapping-reference/term-vector.md +++ b/docs/reference/elasticsearch/mapping-reference/term-vector.md @@ -14,6 +14,8 @@ Term vectors contain information about the terms produced by the [analysis](docs These term vectors can be stored so that they can be retrieved for a particular document. +Refer to the [term vectors API examples](../rest-apis/term-vectors-examples.md) page for usage examples. + The `term_vector` setting accepts: `no` diff --git a/docs/reference/elasticsearch/rest-apis/term-vectors-examples.md b/docs/reference/elasticsearch/rest-apis/term-vectors-examples.md new file mode 100644 index 0000000000000..270393dd8307b --- /dev/null +++ b/docs/reference/elasticsearch/rest-apis/term-vectors-examples.md @@ -0,0 +1,307 @@ +--- +applies_to: + stack: all +navigation_title: Term vectors API examples +--- +# Term vectors API examples + +[Term vectors](/reference/elasticsearch/mapping-reference/term-vector.md) provide information about the terms that were produced by the analysis process, including term frequencies, positions, offsets, and payloads. They're useful for applications like highlighting, more-like-this queries, and text analysis. + +This page shows you examples of using the [term vectors API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-termvectors). + +## Returning stored term vectors [docs-termvectors-api-stored-termvectors] + +First, create an index that stores term vectors, payloads, and so on: + +```console +PUT /my-index-000001 +{ "mappings": { + "properties": { + "text": { + "type": "text", + "term_vector": "with_positions_offsets_payloads", + "store" : true, + "analyzer" : "fulltext_analyzer" + }, + "fullname": { + "type": "text", + "term_vector": "with_positions_offsets_payloads", + "analyzer" : "fulltext_analyzer" + } + } + }, + "settings" : { + "index" : { + "number_of_shards" : 1, + "number_of_replicas" : 0 + }, + "analysis": { + "analyzer": { + "fulltext_analyzer": { + "type": "custom", + "tokenizer": "whitespace", + "filter": [ + "lowercase", + "type_as_payload" + ] + } + } + } + } +} +``` + +Add some documents: + +```console +PUT /my-index-000001/_doc/1 +{ + "fullname" : "John Doe", + "text" : "test test test " +} + +PUT /my-index-000001/_doc/2?refresh=wait_for +{ + "fullname" : "Jane Doe", + "text" : "Another test ..." +} +``` + +% TEST[continued] + +The following request returns all information and statistics for field +`text` in document `1` (John Doe): + +```console +GET /my-index-000001/_termvectors/1 +{ + "fields" : ["text"], + "offsets" : true, + "payloads" : true, + "positions" : true, + "term_statistics" : true, + "field_statistics" : true +} +``` + +% TEST[continued] + +Response: + +```console-result +{ + "_index": "my-index-000001", + "_id": "1", + "_version": 1, + "found": true, + "took": 6, + "term_vectors": { + "text": { + "field_statistics": { + "sum_doc_freq": 4, + "doc_count": 2, + "sum_ttf": 6 + }, + "terms": { + "test": { + "doc_freq": 2, + "ttf": 4, + "term_freq": 3, + "tokens": [ + { + "position": 0, + "start_offset": 0, + "end_offset": 4, + "payload": "d29yZA==" + }, + { + "position": 1, + "start_offset": 5, + "end_offset": 9, + "payload": "d29yZA==" + }, + { + "position": 2, + "start_offset": 10, + "end_offset": 14, + "payload": "d29yZA==" + } + ] + } + } + } + } +} +``` + +% TEST[continued] +% TESTRESPONSE[s/"took": 6/"took": "$body.took"/] + +## Generating term vectors on the fly [docs-termvectors-api-generate-termvectors] + +Term vectors which are not explicitly stored in the index are automatically +computed on the fly. The following request returns all information and statistics for the +fields in document `1`, even though the terms haven't been explicitly stored in the index. +Note that for the field `text`, the terms are not re-generated. + +```console +GET /my-index-000001/_termvectors/1 +{ + "fields" : ["text", "some_field_without_term_vectors"], + "offsets" : true, + "positions" : true, + "term_statistics" : true, + "field_statistics" : true +} +``` + +% TEST[continued] + +## Artificial documents [docs-termvectors-artificial-doc] + +Term vectors can also be generated for artificial documents, +that is for documents not present in the index. For example, the following request would +return the same results as in example 1. The mapping used is determined by the `index`. + +*If dynamic mapping is turned on (default), the document fields not in the original mapping will be dynamically created.* + +```console +GET /my-index-000001/_termvectors +{ + "doc" : { + "fullname" : "John Doe", + "text" : "test test test" + } +} +``` + +% TEST[continued] + +## Per-field analyzer [docs-termvectors-per-field-analyzer] + +Additionally, a different analyzer than the one at the field may be provided +by using the `per_field_analyzer` parameter. This is useful in order to +generate term vectors in any fashion, especially when using artificial +documents. When providing an analyzer for a field that already stores term +vectors, the term vectors will be re-generated. + +```console +GET /my-index-000001/_termvectors +{ + "doc" : { + "fullname" : "John Doe", + "text" : "test test test" + }, + "fields": ["fullname"], + "per_field_analyzer" : { + "fullname": "keyword" + } +} +``` + +% TEST[continued] + +Response: + +```console-result +{ + "_index": "my-index-000001", + "_version": 0, + "found": true, + "took": 6, + "term_vectors": { + "fullname": { + "field_statistics": { + "sum_doc_freq": 2, + "doc_count": 4, + "sum_ttf": 4 + }, + "terms": { + "John Doe": { + "term_freq": 1, + "tokens": [ + { + "position": 0, + "start_offset": 0, + "end_offset": 8 + } + ] + } + } + } + } +} +``` + +% TEST[continued] +% TESTRESPONSE[s/"took": 6/"took": "$body.took"/] +% TESTRESPONSE[s/"sum_doc_freq": 2/"sum_doc_freq": "$body.term_vectors.fullname.field_statistics.sum_doc_freq"/] +% TESTRESPONSE[s/"doc_count": 4/"doc_count": "$body.term_vectors.fullname.field_statistics.doc_count"/] +% TESTRESPONSE[s/"sum_ttf": 4/"sum_ttf": "$body.term_vectors.fullname.field_statistics.sum_ttf"/] + +## Terms filtering [docs-termvectors-terms-filtering] + +Finally, the terms returned could be filtered based on their tf-idf scores. In +the example below we obtain the three most "interesting" keywords from the +artificial document having the given "plot" field value. Notice +that the keyword "Tony" or any stop words are not part of the response, as +their tf-idf must be too low. + +```console +GET /imdb/_termvectors +{ + "doc": { + "plot": "When wealthy industrialist Tony Stark is forced to build an armored suit after a life-threatening incident, he ultimately decides to use its technology to fight against evil." + }, + "term_statistics": true, + "field_statistics": true, + "positions": false, + "offsets": false, + "filter": { + "max_num_terms": 3, + "min_term_freq": 1, + "min_doc_freq": 1 + } +} +``` + +% TEST[skip:no imdb test index] + +Response: + +```console-result +{ + "_index": "imdb", + "_version": 0, + "found": true, + "term_vectors": { + "plot": { + "field_statistics": { + "sum_doc_freq": 3384269, + "doc_count": 176214, + "sum_ttf": 3753460 + }, + "terms": { + "armored": { + "doc_freq": 27, + "ttf": 27, + "term_freq": 1, + "score": 9.74725 + }, + "industrialist": { + "doc_freq": 88, + "ttf": 88, + "term_freq": 1, + "score": 8.590818 + }, + "stark": { + "doc_freq": 44, + "ttf": 47, + "term_freq": 1, + "score": 9.272792 + } + } + } + } +} +``` diff --git a/docs/reference/elasticsearch/toc.yml b/docs/reference/elasticsearch/toc.yml index 625ec7069a024..6d9cd044103fc 100644 --- a/docs/reference/elasticsearch/toc.yml +++ b/docs/reference/elasticsearch/toc.yml @@ -80,26 +80,27 @@ toc: - file: rest-apis/compatibility.md - file: rest-apis/api-examples.md children: - - file: rest-apis/refresh-parameter.md - - file: rest-apis/optimistic-concurrency-control.md - - file: rest-apis/sort-search-results.md - - file: rest-apis/paginate-search-results.md - - file: rest-apis/retrieve-selected-fields.md - - file: rest-apis/search-multiple-data-streams-indices.md - file: rest-apis/collapse-search-results.md + - file: rest-apis/create-index-from-source.md - file: rest-apis/filter-search-results.md - file: rest-apis/highlighting.md - - file: rest-apis/retrieve-inner-hits.md - - file: rest-apis/search-shard-routing.md - - file: rest-apis/searching-with-query-rules.md + - file: rest-apis/optimistic-concurrency-control.md + - file: rest-apis/paginate-search-results.md - file: rest-apis/reciprocal-rank-fusion.md - - file: rest-apis/retrievers.md + - file: rest-apis/refresh-parameter.md - file: rest-apis/reindex-data-stream.md - - file: rest-apis/create-index-from-source.md - - file: rest-apis/shard-request-cache.md - - file: rest-apis/search-suggesters.md + - file: rest-apis/retrieve-inner-hits.md + - file: rest-apis/retrieve-selected-fields.md + - file: rest-apis/retrievers.md + - file: rest-apis/search-multiple-data-streams-indices.md - file: rest-apis/search-profile.md - file: rest-apis/search-rank-eval.md + - file: rest-apis/search-shard-routing.md + - file: rest-apis/search-suggesters.md + - file: rest-apis/sort-search-results.md + - file: rest-apis/searching-with-query-rules.md + - file: rest-apis/shard-request-cache.md + - file: rest-apis/term-vectors-examples.md - file: mapping-reference/index.md children: - file: mapping-reference/document-metadata-fields.md From ce949a85b6b61138b9f71d31b2404e01c3b245ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Thu, 12 Jun 2025 14:25:08 +0200 Subject: [PATCH 089/102] [ESQL] Fix TopNSetTestCase test and unmute it (#129327) Closes https://github.com/elastic/elasticsearch/issues/129257 --- muted-tests.yml | 3 --- .../org/elasticsearch/compute/data/sort/TopNSetTestCase.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/muted-tests.yml b/muted-tests.yml index ea084ec2cd594..ed3971f523f39 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -520,9 +520,6 @@ tests: - class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeForkIT method: test {lookup-join.MultipleBatches* issue: https://github.com/elastic/elasticsearch/issues/129210 -- class: org.elasticsearch.compute.data.sort.LongTopNSetTests - method: testCrankyBreaker - issue: https://github.com/elastic/elasticsearch/issues/129257 # Examples: # diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java index 0df1d6047a30e..a82727301ae04 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/sort/TopNSetTestCase.java @@ -189,7 +189,7 @@ public final void testCrankyBreaker() { collect(sort, value); } - assertResults(sort, sortOrder, limit - 1, values); + assertResults(sort, sortOrder, limit, values); } catch (CircuitBreakingException e) { assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE)); } From 6645bf9db286f91c008d0397a6ef78453f005a87 Mon Sep 17 00:00:00 2001 From: Gal Lalouche Date: Thu, 12 Jun 2025 15:37:08 +0300 Subject: [PATCH 090/102] ESQL: Change queries ID to be the same as the async (#127472) This PR changes the list and query API for ESQL, such that the ID now follows the same format as async query IDs. This is saved as part of the task status. For async queries, this is easy, but for sync queries, this is slightly more complicated, since when creating them, we don't have access to a node ID. So instead, the status itself is just the doc ID portion of the async execution ID, which is used for salting, since this part needs to be consistent, so that when we list the queries, we can compute the async execution ID correctly. Also, I've removed the individual ID, node, and data node tags, as mentioned in the ticket. In addition, I've changed the accept and content-type to be JSON for lists. Resolves #127187 --- docs/changelog/127472.yaml | 6 ++ .../rest-api-spec/api/esql.list_queries.json | 6 +- .../elasticsearch/tasks/TaskAwareRequest.java | 19 ++++++ .../org/elasticsearch/tasks/TaskManager.java | 9 ++- .../xpack/core/async/AsyncExecutionId.java | 13 +++- .../core/async/AsyncExecutionIdWireTests.java | 34 ++++++++++ .../xpack/esql/EsqlSecurityIT.java | 16 +++-- .../src/main/resources/query_task.json | 3 + .../action/AbstractPausableIntegTestCase.java | 3 + .../esql/action/EsqlListQueriesActionIT.java | 65 +++++++++++-------- .../esql/action/EsqlGetQueryRequest.java | 20 +++--- .../xpack/esql/action/EsqlQueryRequest.java | 32 ++++++++- .../action/RestEsqlListQueriesAction.java | 7 +- .../esql/plugin/EsqlGetQueryResponse.java | 9 +-- .../esql/plugin/EsqlListQueriesResponse.java | 8 +-- .../xpack/esql/plugin/EsqlPlugin.java | 1 + .../xpack/esql/plugin/EsqlQueryStatus.java | 44 +++++++++++++ .../plugin/TransportEsqlGetQueryAction.java | 9 +-- .../TransportEsqlListQueriesAction.java | 2 +- .../esql/plugin/TransportEsqlQueryAction.java | 7 +- .../esql/action/EsqlQueryRequestTests.java | 7 +- .../rest-api-spec/test/esql/200_queries.yml | 6 +- 22 files changed, 244 insertions(+), 82 deletions(-) create mode 100644 docs/changelog/127472.yaml create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/async/AsyncExecutionIdWireTests.java create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryStatus.java diff --git a/docs/changelog/127472.yaml b/docs/changelog/127472.yaml new file mode 100644 index 0000000000000..b91288f82bcdd --- /dev/null +++ b/docs/changelog/127472.yaml @@ -0,0 +1,6 @@ +pr: 127472 +summary: Change queries ID to be the same as the async +area: ES|QL +type: feature +issues: + - 127187 diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_queries.json b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_queries.json index 472e70a8766e2..c4f5abcdcb7a3 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_queries.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/esql.list_queries.json @@ -7,10 +7,8 @@ "stability": "experimental", "visibility": "public", "headers": { - "accept": [], - "content_type": [ - "application/json" - ] + "accept": ["application/json"], + "content_type": ["application/json"] }, "url": { "paths": [ diff --git a/server/src/main/java/org/elasticsearch/tasks/TaskAwareRequest.java b/server/src/main/java/org/elasticsearch/tasks/TaskAwareRequest.java index 029e120ce644d..de81e9a1a96ef 100644 --- a/server/src/main/java/org/elasticsearch/tasks/TaskAwareRequest.java +++ b/server/src/main/java/org/elasticsearch/tasks/TaskAwareRequest.java @@ -9,6 +9,8 @@ package org.elasticsearch.tasks; +import org.elasticsearch.core.Nullable; + import java.util.Map; /** @@ -52,6 +54,23 @@ default Task createTask(long id, String type, String action, TaskId parentTaskId return new Task(id, type, action, getDescription(), parentTaskId, headers); } + /** + * Returns the task object that should be used to keep track of the processing of the request, with an extra local node ID. + */ + // TODO remove the above overload, use only this one. + default Task createTask( + // TODO this is only nullable in tests, where the MockNode does not guarantee the localNodeId is set before calling this method. We + // We should fix the tests, and replace this and id with TaskId instead. + @Nullable String localNodeId, + long id, + String type, + String action, + TaskId parentTaskId, + Map headers + ) { + return createTask(id, type, action, parentTaskId, headers); + } + /** * Returns optional description of the request to be displayed by the task manager */ diff --git a/server/src/main/java/org/elasticsearch/tasks/TaskManager.java b/server/src/main/java/org/elasticsearch/tasks/TaskManager.java index d1cf4bd799e4b..249bb1d43119c 100644 --- a/server/src/main/java/org/elasticsearch/tasks/TaskManager.java +++ b/server/src/main/java/org/elasticsearch/tasks/TaskManager.java @@ -141,7 +141,14 @@ public Task register(String type, String action, TaskAwareRequest request, boole headers.put(key, httpHeader); } } - Task task = request.createTask(taskIdGenerator.incrementAndGet(), type, action, request.getParentTask(), headers); + Task task = request.createTask( + lastDiscoveryNodes.getLocalNodeId(), + taskIdGenerator.incrementAndGet(), + type, + action, + request.getParentTask(), + headers + ); Objects.requireNonNull(task); assert task.getParentTaskId().equals(request.getParentTask()) : "Request [ " + request + "] didn't preserve it parentTaskId"; if (logger.isTraceEnabled()) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncExecutionId.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncExecutionId.java index 8316b4cfa605a..2910064744d18 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncExecutionId.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncExecutionId.java @@ -10,6 +10,8 @@ import org.elasticsearch.common.io.stream.ByteBufferStreamInput; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.tasks.TaskId; import java.io.IOException; @@ -20,7 +22,7 @@ /** * A class that contains all information related to a submitted async execution. */ -public final class AsyncExecutionId { +public final class AsyncExecutionId implements Writeable { public static final String ASYNC_EXECUTION_ID_HEADER = "X-Elasticsearch-Async-Id"; public static final String ASYNC_EXECUTION_IS_RUNNING_HEADER = "X-Elasticsearch-Async-Is-Running"; @@ -115,4 +117,13 @@ public static AsyncExecutionId decode(String id) { } return new AsyncExecutionId(docId, new TaskId(taskId), id); } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(getEncoded()); + } + + public static AsyncExecutionId readFrom(StreamInput input) throws IOException { + return decode(input.readString()); + } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/async/AsyncExecutionIdWireTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/async/AsyncExecutionIdWireTests.java new file mode 100644 index 0000000000000..dd78aeb490192 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/async/AsyncExecutionIdWireTests.java @@ -0,0 +1,34 @@ +/* + * 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.core.async; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.test.AbstractWireSerializingTestCase; + +import java.io.IOException; + +public class AsyncExecutionIdWireTests extends AbstractWireSerializingTestCase { + @Override + protected Writeable.Reader instanceReader() { + return AsyncExecutionId::readFrom; + } + + @Override + protected AsyncExecutionId createTestInstance() { + return new AsyncExecutionId(randomAlphaOfLength(15), new TaskId(randomAlphaOfLength(10), randomLong())); + } + + @Override + protected AsyncExecutionId mutateInstance(AsyncExecutionId instance) throws IOException { + return new AsyncExecutionId( + instance.getDocId(), + new TaskId(instance.getTaskId().getNodeId(), instance.getTaskId().getId() * 12345) + ); + } +} diff --git a/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java b/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java index 759b65f7463a2..6494a29e83075 100644 --- a/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java +++ b/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java @@ -916,19 +916,23 @@ public void testListQueryForbidden() throws Exception { public void testGetQueryAllowed() throws Exception { // This is a bit tricky, since there is no such running query. We just make sure it didn't fail on forbidden privileges. - Request request = new Request("GET", "_query/queries/foo:1234"); - var resp = expectThrows(ResponseException.class, () -> client().performRequest(request)); - assertThat(resp.getResponse().getStatusLine().getStatusCode(), not(equalTo(404))); + setUser(GET_QUERY_REQUEST, "user_with_monitor_privileges"); + var resp = expectThrows(ResponseException.class, () -> client().performRequest(GET_QUERY_REQUEST)); + assertThat(resp.getResponse().getStatusLine().getStatusCode(), not(equalTo(403))); } public void testGetQueryForbidden() throws Exception { - Request request = new Request("GET", "_query/queries/foo:1234"); - setUser(request, "user_without_monitor_privileges"); - var resp = expectThrows(ResponseException.class, () -> client().performRequest(request)); + setUser(GET_QUERY_REQUEST, "user_without_monitor_privileges"); + var resp = expectThrows(ResponseException.class, () -> client().performRequest(GET_QUERY_REQUEST)); assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(403)); assertThat(resp.getMessage(), containsString("this action is granted by the cluster privileges [monitor_esql,monitor,manage,all]")); } + private static final Request GET_QUERY_REQUEST = new Request( + "GET", + "_query/queries/FmJKWHpFRi1OU0l5SU1YcnpuWWhoUWcZWDFuYUJBeW1TY0dKM3otWUs2bDJudzo1Mg==" + ); + private void createEnrichPolicy() throws Exception { createIndex("songs", Settings.EMPTY, """ "properties":{"song_id": {"type": "keyword"}, "title": {"type": "keyword"}, "artist": {"type": "keyword"} } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/query_task.json b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/query_task.json index 1da628e0a3e84..f036eec21e9c0 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/query_task.json +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/query_task.json @@ -3,6 +3,9 @@ "id" : 5326, "type" : "transport", "action" : "indices:data/read/esql", + "status" : { + "request_id" : "Ks5ApyqMTtWj5LrKigmCjQ" + }, "description" : "FROM test | STATS MAX(d) by a, b", <1> "start_time" : "2023-07-31T15:46:32.328Z", "start_time_in_millis" : 1690818392328, diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/AbstractPausableIntegTestCase.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/AbstractPausableIntegTestCase.java index 8054b260f0060..86277b1c1cd24 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/AbstractPausableIntegTestCase.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/AbstractPausableIntegTestCase.java @@ -30,6 +30,8 @@ public abstract class AbstractPausableIntegTestCase extends AbstractEsqlIntegTestCase { protected static final Semaphore scriptPermits = new Semaphore(0); + // Incremented onWait. Can be used to check if the onWait process has been reached. + protected static final Semaphore scriptWaits = new Semaphore(0); protected int pageSize = -1; @@ -98,6 +100,7 @@ public void setupIndex() throws IOException { public static class PausableFieldPlugin extends AbstractPauseFieldPlugin { @Override protected boolean onWait() throws InterruptedException { + scriptWaits.release(); return scriptPermits.tryAcquire(1, TimeUnit.MINUTES); } } diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlListQueriesActionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlListQueriesActionIT.java index 590f7efef8037..c7072e217dd5b 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlListQueriesActionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlListQueriesActionIT.java @@ -7,24 +7,21 @@ package org.elasticsearch.xpack.esql.action; +import org.elasticsearch.action.ActionFuture; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; -import org.elasticsearch.tasks.TaskId; import org.elasticsearch.test.IntOrLongMatcher; import org.elasticsearch.test.MapMatcher; import org.elasticsearch.xpack.core.async.GetAsyncResultRequest; import org.elasticsearch.xpack.esql.EsqlTestUtils; -import java.util.List; +import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; import static org.elasticsearch.core.TimeValue.timeValueSeconds; import static org.elasticsearch.xpack.esql.EsqlTestUtils.jsonEntityToMap; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.everyItem; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isA; public class EsqlListQueriesActionIT extends AbstractPausableIntegTestCase { private static final String QUERY = "from test | stats sum(pause_me)"; @@ -45,31 +42,10 @@ public void testRunningQueries() throws Exception { try (var initialResponse = sendAsyncQuery()) { id = initialResponse.asyncExecutionId().get(); + assertRunningQueries(); var getResultsRequest = new GetAsyncResultRequest(id); getResultsRequest.setWaitForCompletionTimeout(timeValueSeconds(1)); client().execute(EsqlAsyncGetResultAction.INSTANCE, getResultsRequest).get().close(); - Response listResponse = getRestClient().performRequest(new Request("GET", "/_query/queries")); - @SuppressWarnings("unchecked") - var listResult = (Map>) EsqlTestUtils.singleValue( - jsonEntityToMap(listResponse.getEntity()).values() - ); - var taskId = new TaskId(EsqlTestUtils.singleValue(listResult.keySet())); - MapMatcher basicMatcher = MapMatcher.matchesMap() - .entry("node", is(taskId.getNodeId())) - .entry("id", IntOrLongMatcher.matches(taskId.getId())) - .entry("query", is(QUERY)) - .entry("start_time_millis", IntOrLongMatcher.isIntOrLong()) - .entry("running_time_nanos", IntOrLongMatcher.isIntOrLong()); - MapMatcher.assertMap(EsqlTestUtils.singleValue(listResult.values()), basicMatcher); - - Response getQueryResponse = getRestClient().performRequest(new Request("GET", "/_query/queries/" + taskId)); - MapMatcher.assertMap( - jsonEntityToMap(getQueryResponse.getEntity()), - basicMatcher.entry("coordinating_node", isA(String.class)) - .entry("data_nodes", allOf(isA(List.class), everyItem(isA(String.class)))) - .entry("documents_found", IntOrLongMatcher.isIntOrLong()) - .entry("values_loaded", IntOrLongMatcher.isIntOrLong()) - ); } finally { if (id != null) { // Finish the query. @@ -82,9 +58,44 @@ public void testRunningQueries() throws Exception { } } + public void testRunningQueriesSync() throws Exception { + var future = sendSyncQueryAsyncly(); + try { + scriptWaits.acquire(); + assertRunningQueries(); + } finally { + scriptPermits.release(numberOfDocs()); + future.actionGet(timeValueSeconds(60)).close(); + } + } + + private static void assertRunningQueries() throws IOException { + Response listResponse = getRestClient().performRequest(new Request("GET", "/_query/queries")); + @SuppressWarnings("unchecked") + var listResult = (Map>) EsqlTestUtils.singleValue(jsonEntityToMap(listResponse.getEntity()).values()); + String queryId = EsqlTestUtils.singleValue(listResult.keySet()); + MapMatcher basicMatcher = MapMatcher.matchesMap() + .entry("query", is(QUERY)) + .entry("start_time_millis", IntOrLongMatcher.isIntOrLong()) + .entry("running_time_nanos", IntOrLongMatcher.isIntOrLong()); + MapMatcher.assertMap(EsqlTestUtils.singleValue(listResult.values()), basicMatcher); + + Response getQueryResponse = getRestClient().performRequest(new Request("GET", "/_query/queries/" + queryId)); + MapMatcher.assertMap( + jsonEntityToMap(getQueryResponse.getEntity()), + basicMatcher.entry("documents_found", IntOrLongMatcher.isIntOrLong()).entry("values_loaded", IntOrLongMatcher.isIntOrLong()) + ); + } + private EsqlQueryResponse sendAsyncQuery() { scriptPermits.drainPermits(); scriptPermits.release(between(1, 5)); return EsqlQueryRequestBuilder.newAsyncEsqlQueryRequestBuilder(client()).query(QUERY).execute().actionGet(60, TimeUnit.SECONDS); } + + private ActionFuture sendSyncQueryAsyncly() { + scriptPermits.drainPermits(); + scriptPermits.release(between(1, 5)); + return EsqlQueryRequestBuilder.newSyncEsqlQueryRequestBuilder(client()).query(QUERY).execute(); + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlGetQueryRequest.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlGetQueryRequest.java index e68626609d94d..96e2ce341b0de 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlGetQueryRequest.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlGetQueryRequest.java @@ -7,33 +7,33 @@ package org.elasticsearch.xpack.esql.action; +import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; -import org.elasticsearch.action.LegacyActionRequest; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.xpack.core.async.AsyncExecutionId; import java.io.IOException; -public class EsqlGetQueryRequest extends LegacyActionRequest { - private final TaskId id; +public class EsqlGetQueryRequest extends ActionRequest { + private final AsyncExecutionId asyncExecutionId; - public EsqlGetQueryRequest(TaskId id) { - this.id = id; + public EsqlGetQueryRequest(AsyncExecutionId asyncExecutionId) { + this.asyncExecutionId = asyncExecutionId; } - public TaskId id() { - return id; + public AsyncExecutionId id() { + return asyncExecutionId; } public EsqlGetQueryRequest(StreamInput streamInput) throws IOException { super(streamInput); - id = TaskId.readFromStream(streamInput); + asyncExecutionId = AsyncExecutionId.decode(streamInput.readString()); } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeWriteable(id); + out.writeWriteable(asyncExecutionId); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java index bd0a7635a4653..d3073d30bfaaf 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.breaker.NoopCircuitBreaker; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; @@ -20,8 +21,10 @@ import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.Task; import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.xpack.core.async.AsyncExecutionId; import org.elasticsearch.xpack.esql.Column; import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plugin.EsqlQueryStatus; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import java.io.IOException; @@ -242,9 +245,32 @@ public EsqlQueryRequest allowPartialResults(boolean allowPartialResults) { } @Override - public Task createTask(long id, String type, String action, TaskId parentTaskId, Map headers) { - // Pass the query as the description - return new CancellableTask(id, type, action, query, parentTaskId, headers); + public Task createTask(String localNodeId, long id, String type, String action, TaskId parentTaskId, Map headers) { + var status = new EsqlQueryStatus(new AsyncExecutionId(UUIDs.randomBase64UUID(), new TaskId(localNodeId, id))); + return new EsqlQueryRequestTask(query, id, type, action, parentTaskId, headers, status); + } + + private static class EsqlQueryRequestTask extends CancellableTask { + private final Status status; + + EsqlQueryRequestTask( + String query, + long id, + String type, + String action, + TaskId parentTaskId, + Map headers, + EsqlQueryStatus status + ) { + // Pass the query as the description + super(id, type, action, query, parentTaskId, headers); + this.status = status; + } + + @Override + public Status getStatus() { + return status; + } } // Setter for tests diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RestEsqlListQueriesAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RestEsqlListQueriesAction.java index 2076ae3b1aa55..ae91b7207f258 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RestEsqlListQueriesAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RestEsqlListQueriesAction.java @@ -15,9 +15,8 @@ import org.elasticsearch.rest.Scope; import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestToXContentListener; -import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.xpack.core.async.AsyncExecutionId; -import java.io.IOException; import java.util.List; import static org.elasticsearch.rest.RestRequest.Method.GET; @@ -37,7 +36,7 @@ public List routes() { } @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { return restChannelConsumer(request, client); } @@ -46,7 +45,7 @@ private static RestChannelConsumer restChannelConsumer(RestRequest request, Node String id = request.param("id"); var action = id != null ? EsqlGetQueryAction.INSTANCE : EsqlListQueriesAction.INSTANCE; - var actionRequest = id != null ? new EsqlGetQueryRequest(new TaskId(id)) : new EsqlListQueriesRequest(); + var actionRequest = id != null ? new EsqlGetQueryRequest(AsyncExecutionId.decode(id)) : new EsqlListQueriesRequest(); return channel -> client.execute(action, actionRequest, new RestToXContentListener<>(channel)); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlGetQueryResponse.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlGetQueryResponse.java index b8679bb95c463..b3d4c543f3f88 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlGetQueryResponse.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlGetQueryResponse.java @@ -14,7 +14,6 @@ import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; -import java.util.List; public class EsqlGetQueryResponse extends ActionResponse implements ToXContentObject { // This is rather limited at the moment, as we don't extract information such as CPU and memory usage, owning user, etc. for the task. @@ -24,22 +23,16 @@ public record DetailedQuery( long runningTimeNanos, long documentsFound, long valuesLoaded, - String query, - String coordinatingNode, - List dataNodes + String query ) implements ToXContentObject { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - builder.field("id", id.getId()); - builder.field("node", id.getNodeId()); builder.field("start_time_millis", startTimeMillis); builder.field("running_time_nanos", runningTimeNanos); builder.field("documents_found", documentsFound); builder.field("values_loaded", valuesLoaded); builder.field("query", query); - builder.field("coordinating_node", coordinatingNode); - builder.field("data_nodes", dataNodes); builder.endObject(); return builder; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlListQueriesResponse.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlListQueriesResponse.java index 383dea8d82d93..a592f7bdb256c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlListQueriesResponse.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlListQueriesResponse.java @@ -9,10 +9,10 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.tasks.TaskId; import org.elasticsearch.xcontent.ToXContentFragment; import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.core.async.AsyncExecutionId; import java.io.IOException; import java.util.List; @@ -20,12 +20,10 @@ public class EsqlListQueriesResponse extends ActionResponse implements ToXContentObject { private final List queries; - public record Query(TaskId taskId, long startTimeMillis, long runningTimeNanos, String query) implements ToXContentFragment { + public record Query(AsyncExecutionId id, long startTimeMillis, long runningTimeNanos, String query) implements ToXContentFragment { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(taskId.toString()); - builder.field("id", taskId.getId()); - builder.field("node", taskId.getNodeId()); + builder.startObject(id.getEncoded()); builder.field("start_time_millis", startTimeMillis); builder.field("running_time_nanos", runningTimeNanos); builder.field("query", query); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java index 6ba7c9747bf07..33178d08b7522 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java @@ -300,6 +300,7 @@ public List getNamedWriteables() { entries.add(AbstractPageMappingOperator.Status.ENTRY); entries.add(AbstractPageMappingToIteratorOperator.Status.ENTRY); entries.add(AggregationOperator.Status.ENTRY); + entries.add(EsqlQueryStatus.ENTRY); entries.add(ExchangeSinkOperator.Status.ENTRY); entries.add(ExchangeSourceOperator.Status.ENTRY); entries.add(HashAggregationOperator.Status.ENTRY); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryStatus.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryStatus.java new file mode 100644 index 0000000000000..bb45b1bf41945 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryStatus.java @@ -0,0 +1,44 @@ +/* + * 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.plugin; + +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.core.async.AsyncExecutionId; + +import java.io.IOException; + +public record EsqlQueryStatus(AsyncExecutionId id) implements Task.Status { + public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( + Task.Status.class, + "EsqlDocIdStatus", + EsqlQueryStatus::new + ); + + @Override + public String getWriteableName() { + return ENTRY.name; + } + + private EsqlQueryStatus(StreamInput stream) throws IOException { + this(AsyncExecutionId.readFrom(stream)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + id.writeTo(out); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("request_id", id.getEncoded()).endObject(); + } +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlGetQueryAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlGetQueryAction.java index 038f9d3df0c6c..a7ebff711c75a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlGetQueryAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlGetQueryAction.java @@ -49,7 +49,7 @@ protected void doExecute(Task task, EsqlGetQueryRequest request, ActionListener< nodeClient, ESQL_ORIGIN, TransportGetTaskAction.TYPE, - new GetTaskRequest().setTaskId(request.id()), + new GetTaskRequest().setTaskId(request.id().getTaskId()), new ActionListener<>() { @Override public void onResponse(GetTaskResponse response) { @@ -64,7 +64,7 @@ public void onResponse(GetTaskResponse response) { TransportListTasksAction.TYPE, new ListTasksRequest().setDetailed(true) .setActions(DriverTaskRunner.ACTION_NAME) - .setTargetParentTaskId(request.id()), + .setTargetParentTaskId(request.id().getTaskId()), new ActionListener<>() { @Override public void onResponse(ListTasksResponse response) { @@ -91,7 +91,6 @@ public void onFailure(Exception e) { private static EsqlGetQueryResponse.DetailedQuery toDetailedQuery(TaskInfo main, ListTasksResponse sub) { String query = main.description(); - String coordinatingNode = main.node(); // TODO include completed drivers in documentsFound and valuesLoaded long documentsFound = 0; @@ -110,9 +109,7 @@ private static EsqlGetQueryResponse.DetailedQuery toDetailedQuery(TaskInfo main, main.runningTimeNanos(), documentsFound, valuesLoaded, - query, - coordinatingNode, - sub.getTasks().stream().map(TaskInfo::node).distinct().toList() // Data nodes + query ); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlListQueriesAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlListQueriesAction.java index 2db7559a669b8..5d113abbcf76e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlListQueriesAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlListQueriesAction.java @@ -72,7 +72,7 @@ public void onFailure(Exception e) { private static EsqlListQueriesResponse.Query toQuery(TaskInfo taskInfo) { return new EsqlListQueriesResponse.Query( - taskInfo.taskId(), + ((EsqlQueryStatus) taskInfo.status()).id(), taskInfo.startTime(), taskInfo.runningTimeNanos(), taskInfo.description() diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java index fe8d200992b0d..673a551884912 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java @@ -408,7 +408,12 @@ public EsqlQueryTask createTask( originHeaders, asyncExecutionId, request.keepAlive() - ); + ) { + @Override + public Status getStatus() { + return new EsqlQueryStatus(asyncExecutionId); + } + }; } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java index 698291a54fa68..16547ed0bfb3a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.xpack.esql.parser.ParsingException; import org.elasticsearch.xpack.esql.parser.QueryParam; import org.elasticsearch.xpack.esql.parser.QueryParams; +import org.elasticsearch.xpack.esql.plugin.EsqlQueryStatus; import java.io.IOException; import java.util.ArrayList; @@ -609,10 +610,10 @@ public void testTask() throws IOException { }""".replace("QUERY", query); EsqlQueryRequest request = parseEsqlQueryRequestSync(requestJson); - Task task = request.createTask(id, "transport", EsqlQueryAction.NAME, TaskId.EMPTY_TASK_ID, Map.of()); + String localNode = randomAlphaOfLength(2); + Task task = request.createTask(localNode, id, "transport", EsqlQueryAction.NAME, TaskId.EMPTY_TASK_ID, Map.of()); assertThat(task.getDescription(), equalTo(query)); - String localNode = randomAlphaOfLength(2); TaskInfo taskInfo = task.taskInfo(localNode, true); String json = taskInfo.toString(); String expected = Streams.readFully(getClass().getClassLoader().getResourceAsStream("query_task.json")).utf8ToString(); @@ -621,6 +622,8 @@ public void testTask() throws IOException { .replaceAll("FROM test \\| STATS MAX\\(d\\) by a, b", query) .replaceAll("5326", Integer.toString(id)) .replaceAll("2j8UKw1bRO283PMwDugNNg", localNode) + .replaceAll("Ks5ApyqMTtWj5LrKigmCjQ", ((EsqlQueryStatus) taskInfo.status()).id().getEncoded()) + .replaceAll("2023-07-31T15:46:32\\.328Z", DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.formatMillis(taskInfo.startTime())) .replaceAll("2023-07-31T15:46:32\\.328Z", DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.formatMillis(taskInfo.startTime())) .replaceAll("1690818392328", Long.toString(taskInfo.startTime())) .replaceAll("41.7ms", TimeValue.timeValueNanos(taskInfo.runningTimeNanos()).toString()) diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_queries.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_queries.yml index acc5f9e79f3cc..57528971855c9 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_queries.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/200_queries.yml @@ -28,13 +28,13 @@ List with no running queries: --- Get with invalid task ID: - do: - catch: /malformed task id foobar/ + catch: /invalid id[:] \[foobar\]|malformed task id foobar/ esql.get_query: id: "foobar" --- Get with non-existent task ID: - do: - catch: /task \[foobar:1234\] belongs to the node \[foobar\] which isn't part of the cluster and there is no record of the task/ + catch: /task \[X1naBAymScGJ3z-YK6l2nw:52\] belongs to the node \[X1naBAymScGJ3z-YK6l2nw\] which isn't part of the cluster and there is no record of the task/ esql.get_query: - id: "foobar:1234" + id: "FmJKWHpFRi1OU0l5SU1YcnpuWWhoUWcZWDFuYUJBeW1TY0dKM3otWUs2bDJudzo1Mg==" From dad2394d2e57e428d5383dfbde5a835118dbdaf5 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Thu, 12 Jun 2025 14:54:50 +0200 Subject: [PATCH 091/102] Adjust unpromotable shard refresh request validation to allow RefreshResult.NO_REFRESH (#129176) When a primary shard uses the read-only engine, it always returns a RefreshResult.NO_REFRESH for refreshes. Since #93600 we added an extra roundtrip to hook unpromotable shard refresh logic. This hook is always executed, even if there are no unpromotable shards, but the UnpromotableShardRefreshRequest would fail if the primary shard returns a RefreshResult.NO_REFRESH result. Fix to be backported to several versions as it's annoying. Closes #129036 --- docs/changelog/129176.yaml | 6 ++++ .../refresh/TransportShardRefreshAction.java | 4 --- ...ansportUnpromotableShardRefreshAction.java | 12 ++++--- .../UnpromotableShardRefreshRequest.java | 4 ++- .../index/engine/ReadOnlyEngine.java | 2 +- .../FrozenSearchableSnapshotsIntegTests.java | 32 ++++++++++++++++++ .../SearchableSnapshotsIntegTests.java | 33 +++++++++++++++++++ 7 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 docs/changelog/129176.yaml diff --git a/docs/changelog/129176.yaml b/docs/changelog/129176.yaml new file mode 100644 index 0000000000000..668cfb68b9f89 --- /dev/null +++ b/docs/changelog/129176.yaml @@ -0,0 +1,6 @@ +pr: 129176 +summary: Adjust unpromotable shard refresh request validation to allow `RefreshResult.NO_REFRESH` +area: Searchable Snapshots +type: bug +issues: + - 129036 diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportShardRefreshAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportShardRefreshAction.java index 2286f64648185..3dc3e19dcb979 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportShardRefreshAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportShardRefreshAction.java @@ -23,7 +23,6 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.injection.guice.Inject; @@ -127,10 +126,7 @@ public void onPrimaryOperationComplete( ActionListener listener ) { var primaryTerm = replicaRequest.primaryRefreshResult.primaryTerm(); - assert Engine.UNKNOWN_PRIMARY_TERM < primaryTerm : primaryTerm; - var generation = replicaRequest.primaryRefreshResult.generation(); - assert Engine.RefreshResult.UNKNOWN_GENERATION < generation : generation; transportService.sendRequest( transportService.getLocalNode(), diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportUnpromotableShardRefreshAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportUnpromotableShardRefreshAction.java index dd4fbedad98a6..283774ed4a439 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportUnpromotableShardRefreshAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportUnpromotableShardRefreshAction.java @@ -22,6 +22,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.engine.Engine; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.injection.guice.Inject; import org.elasticsearch.node.NodeClosedException; @@ -154,12 +155,13 @@ protected void unpromotableShardOperation( return; } + var primaryTerm = request.getPrimaryTerm(); + assert Engine.UNKNOWN_PRIMARY_TERM < primaryTerm : primaryTerm; + var segmentGeneration = request.getSegmentGeneration(); + assert Engine.RefreshResult.UNKNOWN_GENERATION < segmentGeneration : segmentGeneration; + ActionListener.run(responseListener, listener -> { - shard.waitForPrimaryTermAndGeneration( - request.getPrimaryTerm(), - request.getSegmentGeneration(), - listener.map(l -> ActionResponse.Empty.INSTANCE) - ); + shard.waitForPrimaryTermAndGeneration(primaryTerm, segmentGeneration, listener.map(l -> ActionResponse.Empty.INSTANCE)); }); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/UnpromotableShardRefreshRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/UnpromotableShardRefreshRequest.java index 07b2bc1fcf7c1..af6aac3c7fa53 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/UnpromotableShardRefreshRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/refresh/UnpromotableShardRefreshRequest.java @@ -63,7 +63,9 @@ public UnpromotableShardRefreshRequest(StreamInput in) throws IOException { @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = super.validate(); - if (segmentGeneration == Engine.RefreshResult.UNKNOWN_GENERATION) { + if (segmentGeneration == Engine.RefreshResult.UNKNOWN_GENERATION && primaryTerm == Engine.UNKNOWN_PRIMARY_TERM) { + // read-only primary shards (like searchable snapshot shard) return Engine.RefreshResult.NO_REFRESH during refresh + } else if (segmentGeneration == Engine.RefreshResult.UNKNOWN_GENERATION) { validationException = addValidationError("segment generation is unknown", validationException); } return validationException; diff --git a/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java b/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java index 63a4696ddb08e..11ba440a3d6ea 100644 --- a/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java +++ b/server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java @@ -446,7 +446,7 @@ public RefreshResult refresh(String source) { @Override public void maybeRefresh(String source, ActionListener listener) throws EngineException { - listener.onResponse(RefreshResult.NO_REFRESH); + ActionListener.completeWith(listener, () -> refresh(source)); } @Override diff --git a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/FrozenSearchableSnapshotsIntegTests.java b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/FrozenSearchableSnapshotsIntegTests.java index 8f6329be0ccda..5c86ca4649194 100644 --- a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/FrozenSearchableSnapshotsIntegTests.java +++ b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/FrozenSearchableSnapshotsIntegTests.java @@ -67,6 +67,7 @@ import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHistogram; import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOT_STORE_TYPE; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse; import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_RECOVERY_STATE_FACTORY_KEY; import static org.hamcrest.Matchers.containsString; @@ -521,6 +522,37 @@ public void testRequestCacheOnFrozen() throws Exception { } } + public void testRefreshPartiallyMountedIndex() throws Exception { + internalCluster().ensureAtLeastNumDataNodes(2); + + final var index = "index"; + createIndex(index, 1, 0); + populateIndex(index, 1_000); + + final var repository = "repository"; + createRepository(repository, FsRepository.TYPE, Settings.builder().put("location", randomRepoPath())); + + final var snapshot = "repository"; + createFullSnapshot(repository, snapshot); + + assertAcked(indicesAdmin().prepareDelete(index)); + + final var partialIndex = "partial-" + index; + mountSnapshot( + repository, + snapshot, + index, + partialIndex, + Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, randomInt(1)).build(), + MountSearchableSnapshotRequest.Storage.SHARED_CACHE + ); + ensureGreen(partialIndex); + + // before the fix this would have failed + var refreshResult = indicesAdmin().prepareRefresh(partialIndex).execute().actionGet(); + assertNoFailures(refreshResult); + } + public void testTierPreferenceCannotBeRemovedForFrozenIndex() throws Exception { final String fsRepoName = randomAlphaOfLength(10); final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); diff --git a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java index 22c4fc17a1911..16543a30238b3 100644 --- a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java +++ b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java @@ -51,6 +51,7 @@ import org.elasticsearch.index.shard.IndexLongFieldRange; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.repositories.RepositoryData; +import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchResponseUtils; import org.elasticsearch.snapshots.SnapshotId; @@ -89,6 +90,7 @@ import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOT_STORE_TYPE; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_RECOVERY_STATE_FACTORY_KEY; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; @@ -1280,6 +1282,37 @@ public void onFailure(Exception e) { } } + public void testRefreshFullyMountedIndex() throws Exception { + internalCluster().ensureAtLeastNumDataNodes(2); + + final var index = "index"; + createIndex(index, 1, 0); + populateIndex(index, 1_000); + + final var repository = "repository"; + createRepository(repository, FsRepository.TYPE, Settings.builder().put("location", randomRepoPath())); + + final var snapshot = "repository"; + createFullSnapshot(repository, snapshot); + + assertAcked(indicesAdmin().prepareDelete(index)); + + final var fullIndex = "full-" + index; + mountSnapshot( + repository, + snapshot, + index, + fullIndex, + Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, randomInt(1)).build(), + MountSearchableSnapshotRequest.Storage.FULL_COPY + ); + ensureGreen(fullIndex); + + // before the fix this would have failed + var refreshResult = indicesAdmin().prepareRefresh(fullIndex).execute().actionGet(); + assertNoFailures(refreshResult); + } + private TaskInfo getTaskForActionFromMaster(String action) { ListTasksResponse response = client().execute( TransportListTasksAction.TYPE, From 60108982ed9bd2dc727e439510a408d8ca766a71 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 12 Jun 2025 23:09:54 +1000 Subject: [PATCH 092/102] Add a Multi-Project Search Rest Test (#128657) This commit adds a Rest IT specifically for search in MultiProject. Everything was already working as expected, but we were a bit light on explicit testing for search, which as _the_ core capability of Elasticsearch is worth testing thoroughly and clearly. --- .../admin/indices/SearchMultiProjectIT.java | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/action/admin/indices/SearchMultiProjectIT.java diff --git a/test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/action/admin/indices/SearchMultiProjectIT.java b/test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/action/admin/indices/SearchMultiProjectIT.java new file mode 100644 index 0000000000000..445fe7de2b415 --- /dev/null +++ b/test/external-modules/multi-project/src/javaRestTest/java/org/elasticsearch/action/admin/indices/SearchMultiProjectIT.java @@ -0,0 +1,291 @@ +/* + * 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.action.admin.indices; + +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.cluster.metadata.ProjectId; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.multiproject.MultiProjectRestTestCase; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.local.LocalClusterSpecBuilder; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; +import org.elasticsearch.test.rest.ObjectPath; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.rules.TestName; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; + +public class SearchMultiProjectIT extends MultiProjectRestTestCase { + + private static final String PASSWORD = "hunter2"; + + @ClassRule + public static ElasticsearchCluster cluster = createCluster(); + + @Rule + public final TestName testNameRule = new TestName(); + + private static ElasticsearchCluster createCluster() { + LocalClusterSpecBuilder clusterBuilder = ElasticsearchCluster.local() + .nodes(1) + .distribution(DistributionType.INTEG_TEST) + .module("test-multi-project") + .setting("test.multi_project.enabled", "true") + .setting("xpack.security.enabled", "true") + .user("admin", PASSWORD); + return clusterBuilder.build(); + } + + @Override + protected String getTestRestCluster() { + return cluster.getHttpAddresses(); + } + + @Override + protected Settings restClientSettings() { + final String token = basicAuthHeaderValue("admin", new SecureString(PASSWORD.toCharArray())); + return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); + } + + public void testSearchIndexThatExistsInMultipleProjects() throws Exception { + final ProjectId projectId1 = ProjectId.fromId(randomIdentifier()); + createProject(projectId1.id()); + + final ProjectId projectId2 = ProjectId.fromId(randomIdentifier()); + createProject(projectId2.id()); + + final String indexPrefix = getTestName().toLowerCase(Locale.ROOT); + final String indexName = indexPrefix + "-" + randomAlphanumericOfLength(6).toLowerCase(Locale.ROOT); + + createIndex(projectId1, indexName); + String docId1 = putDocument(projectId1, indexName, "{\"project\": 1 }", true); + + createIndex(projectId2, indexName); + String docId2a = putDocument(projectId2, indexName, "{\"project\": 2, \"doc\": \"a\" }", false); + String docId2b = putDocument(projectId2, indexName, "{\"project\": 2, \"doc\": \"b\" }", true); + + List results1 = search(projectId1, indexName); + assertThat(results1, containsInAnyOrder(docId1)); + + List results2 = search(projectId2, indexName); + assertThat(results2, containsInAnyOrder(docId2a, docId2b)); + + final var query = """ + { + "query": { "term": { "project": 1 } } + } + """; + results1 = getHitIds(search(projectId1, indexPrefix + "-*", query)); + assertThat(results1, containsInAnyOrder(docId1)); + + results2 = getHitIds(search(projectId2, indexPrefix + "-*", query)); + assertThat(results2, empty()); + + final String aliasName = indexPrefix + "-" + randomIntBetween(100, 999); + addAlias(projectId1, indexName, aliasName); + + results1 = search(projectId1, aliasName); + assertThat(results1, containsInAnyOrder(docId1)); + + assertIndexNotFound(projectId2, aliasName); + + addAlias(projectId2, indexName, aliasName); + results2 = search(projectId2, indexName); + assertThat(results2, containsInAnyOrder(docId2a, docId2b)); + + results1 = search(projectId1, indexPrefix + "-*"); + assertThat(results1, containsInAnyOrder(docId1)); + } + + public void testIndexNotVisibleAcrossProjects() throws IOException { + final ProjectId projectId1 = ProjectId.fromId(randomIdentifier()); + createProject(projectId1.id()); + + final ProjectId projectId2 = ProjectId.fromId(randomIdentifier()); + createProject(projectId2.id()); + + final String indexPrefix = getTestName().toLowerCase(Locale.ROOT); + final String indexName = indexPrefix + "-" + randomAlphanumericOfLength(6).toLowerCase(Locale.ROOT); + + createIndex(projectId1, indexName); + String docId1 = putDocument(projectId1, indexName, "{\"project\": 1 }", true); + + List results1 = search(projectId1, indexName); + assertThat(results1, containsInAnyOrder(docId1)); + + assertIndexNotFound(projectId2, indexName); + + results1 = search(projectId1, indexPrefix + "-*"); + assertThat(results1, containsInAnyOrder(docId1)); + + List results2 = search(projectId2, indexPrefix + "-*"); + assertThat(results2, empty()); + + results2 = search(projectId2, ""); + assertThat(results2, empty()); + } + + public void testRequestCacheIsNotSharedAcrossProjects() throws IOException { + final ProjectId projectId1 = ProjectId.fromId(randomIdentifier()); + createProject(projectId1.id()); + + final ProjectId projectId2 = ProjectId.fromId(randomIdentifier()); + createProject(projectId2.id()); + + final String indexPrefix = getTestName().toLowerCase(Locale.ROOT); + final String indexName = indexPrefix + "-" + randomAlphanumericOfLength(6).toLowerCase(Locale.ROOT); + + createIndex(projectId1, indexName); + putDocument(projectId1, indexName, "{\"project\": 1 }", true); + + createIndex(projectId2, indexName); + putDocument(projectId2, indexName, "{\"project\": 2, \"doc\": \"a\" }", false); + putDocument(projectId2, indexName, "{\"project\": 2, \"doc\": \"b\" }", false); + putDocument(projectId2, indexName, "{\"project\": 2, \"doc\": \"c\" }", true); + + final long initialCacheSize = getRequestCacheUsage(); + + final var query = """ + { + "size": 0, + "aggs": { + "proj": { "terms": { "field": "project" } } + } + } + """; + + // Perform a search in project 1 that should be cached in shard request cache + // That is, an aggregation with size:0 + ObjectPath response = search(projectId1, indexName, query); + String context = "In search response: " + response; + assertThat(context, response.evaluateArraySize("aggregations.proj.buckets"), equalTo(1)); + assertThat(context, response.evaluate("aggregations.proj.buckets.0.key"), equalTo(1)); + assertThat(context, response.evaluate("aggregations.proj.buckets.0.doc_count"), equalTo(1)); + + final long agg1CacheSize = getRequestCacheUsage(); + assertThat("Expected aggregation result to be stored in shard request cache", agg1CacheSize, greaterThan(initialCacheSize)); + + // Perform the identical search on project 2 and make sure it returns the right results for the project + response = search(projectId2, indexName, query); + context = "In search response: " + response; + assertThat(context, response.evaluateArraySize("aggregations.proj.buckets"), equalTo(1)); + assertThat(context, response.evaluate("aggregations.proj.buckets.0.key"), equalTo(2)); + assertThat(context, response.evaluate("aggregations.proj.buckets.0.doc_count"), equalTo(3)); + + final long agg2CacheSize = getRequestCacheUsage(); + assertThat("Expected aggregation result to be stored in shard request cache", agg2CacheSize, greaterThan(agg1CacheSize)); + } + + private void createIndex(ProjectId projectId, String indexName) throws IOException { + Request request = new Request("PUT", "/" + indexName); + setRequestProjectId(request, projectId.id()); + Response response = client().performRequest(request); + assertOK(response); + } + + private void addAlias(ProjectId projectId, String indexName, String alias) throws IOException { + Request request = new Request("POST", "/_aliases"); + request.setJsonEntity(Strings.format(""" + { + "actions": [ + { + "add": { + "index": "%s", + "alias": "%s" + } + } + ] + } + """, indexName, alias)); + setRequestProjectId(request, projectId.id()); + Response response = client().performRequest(request); + assertOK(response); + } + + private String putDocument(ProjectId projectId, String indexName, String body, boolean refresh) throws IOException { + Request request = new Request("POST", "/" + indexName + "/_doc?refresh=" + refresh); + request.setJsonEntity(body); + setRequestProjectId(request, projectId.id()); + Response response = client().performRequest(request); + assertOK(response); + return String.valueOf(entityAsMap(response).get("_id")); + } + + private List search(ProjectId projectId, String indexExpression) throws IOException { + return getHitIds(search(projectId, indexExpression, null)); + } + + private static ObjectPath search(ProjectId projectId, String indexExpression, String body) throws IOException { + Request request = new Request("GET", "/" + indexExpression + "/_search"); + if (body != null) { + request.setJsonEntity(body); + } + setRequestProjectId(request, projectId.id()); + Response response = client().performRequest(request); + assertOK(response); + return new ObjectPath(entityAsMap(response)); + } + + private void assertIndexNotFound(ProjectId projectId2, String indexName) { + ResponseException ex = expectThrows(ResponseException.class, () -> search(projectId2, indexName)); + assertThat(ex.getMessage(), containsString("index_not_found")); + assertThat(ex.getMessage(), containsString(indexName)); + } + + private static List getHitIds(ObjectPath searchResponse) throws IOException { + List> ids = searchResponse.evaluate("hits.hits"); + return ids.stream().map(o -> String.valueOf(o.get("_id"))).toList(); + } + + private long getRequestCacheUsage() throws IOException { + final ObjectPath nodeStats = getNodeStats("indices/request_cache"); + return evaluateLong(nodeStats, "indices.request_cache.memory_size_in_bytes"); + } + + private static ObjectPath getNodeStats(String stat) throws IOException { + Request request = new Request("GET", "/_nodes/stats/" + stat); + Response response = client().performRequest(request); + assertOK(response); + final Map responseMap = entityAsMap(response); + + @SuppressWarnings("unchecked") + final Map nodes = (Map) responseMap.get("nodes"); + assertThat(nodes, aMapWithSize(1)); + + ObjectPath nodeStats = new ObjectPath(nodes.values().iterator().next()); + return nodeStats; + } + + private static long evaluateLong(ObjectPath nodeStats, String path) throws IOException { + Object size = nodeStats.evaluate(path); + assertThat("did not find " + path + " in " + nodeStats, size, notNullValue()); + assertThat("incorrect type for " + path + " in " + nodeStats, size, instanceOf(Number.class)); + return ((Number) size).longValue(); + } + +} From 194a22136162f02095ea94fb914b5576441cdf98 Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 16:04:38 +0100 Subject: [PATCH 093/102] Modified LinearRetriever to include minScore --- .../rank/linear/LinearRetrieverBuilder.java | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java index 436096523a1ec..8a9ad102342c2 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java @@ -125,12 +125,34 @@ public LinearRetrieverBuilder( this.normalizers = normalizers; } + public LinearRetrieverBuilder( + List innerRetrievers, + int rankWindowSize, + float[] weights, + ScoreNormalizer[] normalizers, + Float minScore, + String retrieverName, + List preFilterQueryBuilders + ) { + this (innerRetrievers, rankWindowSize, weights, normalizers); + this.minScore = minScore; + if (minScore != null && minScore < 0) { + throw new IllegalArgumentException("[min_score] must be greater than or equal to 0, was: [" + minScore + "]"); + } + this.retrieverName = retrieverName; + this.preFilterQueryBuilders = preFilterQueryBuilders; + } + @Override protected LinearRetrieverBuilder clone(List newChildRetrievers, List newPreFilterQueryBuilders) { - LinearRetrieverBuilder clone = new LinearRetrieverBuilder(newChildRetrievers, rankWindowSize, weights, normalizers); - clone.preFilterQueryBuilders = newPreFilterQueryBuilders; - clone.retrieverName = retrieverName; - return clone; + return new LinearRetrieverBuilder( + newChildRetrievers, + rankWindowSize, + weights, + normalizers, + minScore, + retrieverName, + newPreFilterQueryBuilders); } @Override @@ -181,6 +203,12 @@ protected RankDoc[] combineInnerRetrieverResults(List rankResults, b topResults[rank] = sortedResults[rank]; topResults[rank].rank = rank + 1; } + // Filter by minScore if set(inclusive) + if(minScore != null) { + topResults = Arrays.stream(topResults) + .filter(doc -> doc.score >= minScore) + .toArray(LinearRankDoc[]::new); + } return topResults; } @@ -205,4 +233,19 @@ public void doToXContent(XContentBuilder builder, Params params) throws IOExcept } builder.field(RANK_WINDOW_SIZE_FIELD.getPreferredName(), rankWindowSize); } + + @Override + public boolean doEquals(Object Other) { + LinearRetrieverBuilder that = (LinearRetrieverBuilder) other; + return super.doEquals(other) + && rankWindowSize == that.rankWindowSize + && weights == that.weights + && Objects.equals(normalizers, that.normalizers) + && Object.equals(minScore, that.minScore); + } + + @Override + public int doHashCode() { + return Objects.hash(rankWindowSize, weights, normalizers, minScore); + } } From c9617233daf8ae8a0b66b0a643d7ae22743dad97 Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 16:12:25 +0100 Subject: [PATCH 094/102] cleaned up --- .../rank/linear/LinearRetrieverBuilder.java | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java index 8a9ad102342c2..7e0c9429c1a2e 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java @@ -134,7 +134,7 @@ public LinearRetrieverBuilder( String retrieverName, List preFilterQueryBuilders ) { - this (innerRetrievers, rankWindowSize, weights, normalizers); + this(innerRetrievers, rankWindowSize, weights, normalizers); this.minScore = minScore; if (minScore != null && minScore < 0) { throw new IllegalArgumentException("[min_score] must be greater than or equal to 0, was: [" + minScore + "]"); @@ -147,12 +147,13 @@ public LinearRetrieverBuilder( protected LinearRetrieverBuilder clone(List newChildRetrievers, List newPreFilterQueryBuilders) { return new LinearRetrieverBuilder( newChildRetrievers, - rankWindowSize, - weights, - normalizers, + rankWindowSize, + weights, + normalizers, minScore, retrieverName, - newPreFilterQueryBuilders); + newPreFilterQueryBuilders + ); } @Override @@ -204,10 +205,8 @@ protected RankDoc[] combineInnerRetrieverResults(List rankResults, b topResults[rank].rank = rank + 1; } // Filter by minScore if set(inclusive) - if(minScore != null) { - topResults = Arrays.stream(topResults) - .filter(doc -> doc.score >= minScore) - .toArray(LinearRankDoc[]::new); + if (minScore != null) { + topResults = Arrays.stream(topResults).filter(doc -> doc.score >= minScore).toArray(LinearRankDoc[]::new); } return topResults; } @@ -233,19 +232,4 @@ public void doToXContent(XContentBuilder builder, Params params) throws IOExcept } builder.field(RANK_WINDOW_SIZE_FIELD.getPreferredName(), rankWindowSize); } - - @Override - public boolean doEquals(Object Other) { - LinearRetrieverBuilder that = (LinearRetrieverBuilder) other; - return super.doEquals(other) - && rankWindowSize == that.rankWindowSize - && weights == that.weights - && Objects.equals(normalizers, that.normalizers) - && Object.equals(minScore, that.minScore); - } - - @Override - public int doHashCode() { - return Objects.hash(rankWindowSize, weights, normalizers, minScore); - } } From a7a0ba3f1a6287b939353102aef9d052eb530f0f Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 16:16:41 +0100 Subject: [PATCH 095/102] Made the same changes we did in textSimilarity --- .../search/retriever/CompoundRetrieverBuilder.java | 3 ++- .../search/retriever/RankDocsRetrieverBuilder.java | 12 +++++++----- .../retriever/RankDocsRetrieverBuilderTests.java | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java index 6cf0af0ef1541..3f9353251b920 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java @@ -195,7 +195,8 @@ public void onFailure(Exception e) { RankDocsRetrieverBuilder rankDocsRetrieverBuilder = new RankDocsRetrieverBuilder( rankWindowSize, newRetrievers.stream().map(s -> s.retriever).toList(), - results::get + results::get, + this.minScore ); rankDocsRetrieverBuilder.retrieverName(retrieverName()); return rankDocsRetrieverBuilder; diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java index a77f5327fbc26..13078f614f8a9 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java @@ -33,13 +33,14 @@ public class RankDocsRetrieverBuilder extends RetrieverBuilder { final List sources; final Supplier rankDocs; - public RankDocsRetrieverBuilder(int rankWindowSize, List sources, Supplier rankDocs) { + public RankDocsRetrieverBuilder(int rankWindowSize, List sources, Supplier rankDocs, Float minScore) { this.rankWindowSize = rankWindowSize; this.rankDocs = rankDocs; if (sources == null || sources.isEmpty()) { throw new IllegalArgumentException("sources must not be null or empty"); } this.sources = sources; + this.minScore = minScore; } @Override @@ -48,7 +49,7 @@ public String getName() { } private boolean sourceHasMinScore() { - return minScore != null || sources.stream().anyMatch(x -> x.minScore() != null); + return this.minScore != null || sources.stream().anyMatch(x -> x.minScore() != null); } private boolean sourceShouldRewrite(QueryRewriteContext ctx) throws IOException { @@ -132,7 +133,7 @@ public void extractToSearchSourceBuilder(SearchSourceBuilder searchSourceBuilder searchSourceBuilder.size(rankWindowSize); } if (sourceHasMinScore()) { - searchSourceBuilder.minScore(this.minScore() == null ? Float.MIN_VALUE : this.minScore()); + searchSourceBuilder.minScore(this.minScore == null ? Float.MIN_VALUE : this.minScore); } if (searchSourceBuilder.size() + searchSourceBuilder.from() > rankDocResults.length) { searchSourceBuilder.size(Math.max(0, rankDocResults.length - searchSourceBuilder.from())); @@ -160,12 +161,13 @@ protected boolean doEquals(Object o) { RankDocsRetrieverBuilder other = (RankDocsRetrieverBuilder) o; return rankWindowSize == other.rankWindowSize && Arrays.equals(rankDocs.get(), other.rankDocs.get()) - && sources.equals(other.sources); + && sources.equals(other.sources) + && Object.equals(minScore, other.minScore); } @Override protected int doHashCode() { - return Objects.hash(super.hashCode(), rankWindowSize, Arrays.hashCode(rankDocs.get()), sources); + return Objects.hash(super.hashCode(), rankWindowSize, Arrays.hashCode(rankDocs.get()), sources, minScore); } @Override diff --git a/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java b/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java index eafab1d25c38e..165ad9b2de183 100644 --- a/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java @@ -97,7 +97,7 @@ private List preFilters(QueryRewriteContext queryRewriteContext) t } private RankDocsRetrieverBuilder createRandomRankDocsRetrieverBuilder(QueryRewriteContext queryRewriteContext) throws IOException { - return new RankDocsRetrieverBuilder(randomIntBetween(1, 100), innerRetrievers(queryRewriteContext), rankDocsSupplier()); + return new RankDocsRetrieverBuilder(randomIntBetween(1, 100), innerRetrievers(queryRewriteContext), rankDocsSupplier(), null); } public void testExtractToSearchSourceBuilder() throws IOException { From c6a12dff9ec0b083ae79955e133e6d2747242d9f Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 16:20:01 +0100 Subject: [PATCH 096/102] Fixed a minor error --- .../search/retriever/RankDocsRetrieverBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java index 13078f614f8a9..50e2d1018e928 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java @@ -162,7 +162,7 @@ protected boolean doEquals(Object o) { return rankWindowSize == other.rankWindowSize && Arrays.equals(rankDocs.get(), other.rankDocs.get()) && sources.equals(other.sources) - && Object.equals(minScore, other.minScore); + && Objects.equals(minScore, other.minScore); } @Override From 5180d513009e9d33fc063038e13f91b4c24bb2bb Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 16:27:02 +0100 Subject: [PATCH 097/102] cleaned up --- .../search/retriever/RankDocsRetrieverBuilder.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java index 50e2d1018e928..0cdd5ab35adcd 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java @@ -161,13 +161,12 @@ protected boolean doEquals(Object o) { RankDocsRetrieverBuilder other = (RankDocsRetrieverBuilder) o; return rankWindowSize == other.rankWindowSize && Arrays.equals(rankDocs.get(), other.rankDocs.get()) - && sources.equals(other.sources) - && Objects.equals(minScore, other.minScore); + && sources.equals(other.sources); } @Override protected int doHashCode() { - return Objects.hash(super.hashCode(), rankWindowSize, Arrays.hashCode(rankDocs.get()), sources, minScore); + return Objects.hash(super.hashCode(), rankWindowSize, Arrays.hashCode(rankDocs.get()), sources); } @Override From a24f243863aa3823bcabd4d1598dbcd7da343f31 Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 17:22:51 +0100 Subject: [PATCH 098/102] Minscore is working :) --- .../xpack/rank/RankRRFFeatures.java | 3 +- .../rank/linear/LinearRetrieverBuilder.java | 2 + .../test/linear/10_linear_retriever.yml | 105 ++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java index 053013064b317..7ae8abe140c03 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java @@ -14,6 +14,7 @@ import static org.elasticsearch.search.retriever.CompoundRetrieverBuilder.INNER_RETRIEVERS_FILTER_SUPPORT; import static org.elasticsearch.xpack.rank.linear.L2ScoreNormalizer.LINEAR_RETRIEVER_L2_NORM; +import static org.elasticsearch.xpack.rank.linear.LinearRetrieverBuilder.LINEAR_RETRIEVER_MINSCORE_FIX; import static org.elasticsearch.xpack.rank.linear.MinMaxScoreNormalizer.LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX; public class RankRRFFeatures implements FeatureSpecification { @@ -27,6 +28,6 @@ public Set getFeatures() { @Override public Set getTestFeatures() { - return Set.of(INNER_RETRIEVERS_FILTER_SUPPORT, LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX, LINEAR_RETRIEVER_L2_NORM); + return Set.of(INNER_RETRIEVERS_FILTER_SUPPORT, LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX, LINEAR_RETRIEVER_L2_NORM, LINEAR_RETRIEVER_MINSCORE_FIX); } } diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java index 7e0c9429c1a2e..518160ded9634 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.rank.linear; import org.apache.lucene.search.ScoreDoc; +import org.elasticsearch.features.NodeFeature; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.util.Maps; import org.elasticsearch.index.query.QueryBuilder; @@ -46,6 +47,7 @@ */ public final class LinearRetrieverBuilder extends CompoundRetrieverBuilder { + public static final NodeFeature LINEAR_RETRIEVER_MINSCORE_FIX = new NodeFeature("linear_retriever_minscore_fix"); public static final String NAME = "linear"; public static final ParseField RETRIEVERS_FIELD = new ParseField("retrievers"); diff --git a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml index 8797187940b52..1e677a152d693 100644 --- a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml +++ b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml @@ -1197,6 +1197,111 @@ setup: rank_window_size: -10 - match: { status: 400 } +--- +"linear retriever respects min_score after normalization": + + - requires: + cluster_features: [ "linear_retriever_minscore_fix" ] + reason: test min_score functionality for linear retriever + + - do: + search: + index: test + body: + retriever: + linear: + retrievers: + - retriever: + standard: + query: + function_score: + query: + match_all: {} + functions: + - filter: { term: { _id: "1" } } + weight: 1 + - filter: { term: { _id: "2" } } + weight: 2 + - filter: { term: { _id: "3" } } + weight: 3 + - filter: { term: { _id: "4" } } + weight: 4 + weight: 1.0 + normalizer: "minmax" + rank_window_size: 10 + min_score: 0.8 + size: 10 + + - match: { hits.total.value: 1 } + - length: { hits.hits: 1 } + - match: { hits.hits.0._id: "4" } + +--- +"linear retriever with min_score zero includes all docs": + + - requires: + cluster_features: [ "linear_retriever_minscore_fix" ] + reason: test min score functionality for linear retriever + + - do: + search: + index: test + body: + retriever: + linear: + retrievers: [ + { + retriever: { + standard: { + query: { + match_all: {} + } + } + }, + weight: 1.0, + normalizer: "minmax" + } + ] + rank_window_size: 10 + min_score: 0 + size: 10 + + - match: { hits.total.value: 4 } + - length: { hits.hits: 4 } + +--- +"linear retriever with high min_score excludes all docs": + + - requires: + cluster_features: [ "linear_retriever_minscore_fix" ] + reason: test min score functionality for linear retriever + + - do: + search: + index: test + body: + retriever: + linear: + retrievers: [ + { + retriever: { + standard: { + query: { + match_all: {} + } + } + }, + weight: 1.0, + normalizer: "minmax" + } + ] + rank_window_size: 10 + min_score: 2.0 + size: 10 + + - match: { hits.total.value: 0 } + - length: { hits.hits: 0 } + --- "minmax normalization properly handles a single doc result set": - requires: From f57a7931447ee370fd4ae11c91e8e8a3490ece37 Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 17:31:52 +0100 Subject: [PATCH 099/102] chore: empty commit to trigger PR update From e789e51673356c1dfc8660db24d8b259bdae3f1a Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 17:53:32 +0100 Subject: [PATCH 100/102] Update docs/changelog/129359.yaml --- docs/changelog/129359.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/129359.yaml diff --git a/docs/changelog/129359.yaml b/docs/changelog/129359.yaml new file mode 100644 index 0000000000000..9b1f6234d6579 --- /dev/null +++ b/docs/changelog/129359.yaml @@ -0,0 +1,5 @@ +pr: 129359 +summary: Add min score linear retriever +area: Search +type: enhancement +issues: [] From 8939675db21d36441a2d08a6b760c1a06b346102 Mon Sep 17 00:00:00 2001 From: Mridula Date: Thu, 12 Jun 2025 18:05:37 +0100 Subject: [PATCH 101/102] Update 10_linear_retriever.yml --- .../rest-api-spec/test/linear/10_linear_retriever.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml index 1e677a152d693..f62c7e4987046 100644 --- a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml +++ b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/linear/10_linear_retriever.yml @@ -319,8 +319,7 @@ setup: - close_to: { hits.hits.2._score: { value: 1.6, error: 0.001 } } - match: { hits.hits.3._id: "3" } - close_to: { hits.hits.3._score: { value: 1.2, error: 0.001} } - - close_to: { hits.hits.3._score: { value: 1.2, error: 0.001 } } - + --- "should handle all zero scores in normalization": - requires: From d200742e124da34f91292b7b1d9a9fe928d7a7c2 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 12 Jun 2025 17:15:42 +0000 Subject: [PATCH 102/102] [CI] Auto commit changes from spotless --- .../java/org/elasticsearch/xpack/rank/RankRRFFeatures.java | 7 ++++++- .../xpack/rank/linear/LinearRetrieverBuilder.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java index 7ae8abe140c03..00395ebd18239 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/RankRRFFeatures.java @@ -28,6 +28,11 @@ public Set getFeatures() { @Override public Set getTestFeatures() { - return Set.of(INNER_RETRIEVERS_FILTER_SUPPORT, LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX, LINEAR_RETRIEVER_L2_NORM, LINEAR_RETRIEVER_MINSCORE_FIX); + return Set.of( + INNER_RETRIEVERS_FILTER_SUPPORT, + LINEAR_RETRIEVER_MINMAX_SINGLE_DOC_FIX, + LINEAR_RETRIEVER_L2_NORM, + LINEAR_RETRIEVER_MINSCORE_FIX + ); } } diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java index 518160ded9634..7631446ca71d0 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/linear/LinearRetrieverBuilder.java @@ -8,9 +8,9 @@ package org.elasticsearch.xpack.rank.linear; import org.apache.lucene.search.ScoreDoc; -import org.elasticsearch.features.NodeFeature; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.util.Maps; +import org.elasticsearch.features.NodeFeature; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.search.builder.SearchSourceBuilder;