From 12fb2fab5676fc3ee516b4eb695da0ffc204eeda Mon Sep 17 00:00:00 2001 From: Mridula Date: Mon, 2 Jun 2025 21:00:49 +0100 Subject: [PATCH 01/46] 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 02/46] 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 03/46] 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 04/46] 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 05/46] [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 06/46] 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 07/46] 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 08/46] 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 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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 16/46] 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 17/46] 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 18/46] 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 19/46] 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 20/46] 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 21/46] 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 22/46] 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 23/46] 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 24/46] 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 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] [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 30/46] 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 31/46] 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 32/46] 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 33/46] 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 34/46] 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 35/46] 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 36/46] 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 37/46] 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 38/46] 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 39/46] 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 ae4f6e4e0952468bc8a7122f9fca45eef1fbae47 Mon Sep 17 00:00:00 2001 From: Mridula Date: Tue, 10 Jun 2025 18:34:05 +0100 Subject: [PATCH 40/46] Included pinned retriever in 9.1 docs --- .../elasticsearch/rest-apis/retrievers.md | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/docs/reference/elasticsearch/rest-apis/retrievers.md b/docs/reference/elasticsearch/rest-apis/retrievers.md index 029f50a4ad0ce..f7423ae981387 100644 --- a/docs/reference/elasticsearch/rest-apis/retrievers.md +++ b/docs/reference/elasticsearch/rest-apis/retrievers.md @@ -44,6 +44,76 @@ The following retrievers are available: `rule` : A [retriever](#rule-retriever) that applies contextual [Searching with query rules](/reference/elasticsearch/rest-apis/searching-with-query-rules.md#query-rules) to pin or exclude documents for specific queries. +## Pinned Retriever [pinned-retriever] + +A `pinned` retriever returns top documents by always placing specific documents at the top of the results, in the order provided. This is useful for promoting certain documents for particular queries, regardless of their relevance score. The remaining results are filled by a fallback retriever. + +#### Parameters [pinned-retriever-parameters] + +`ids` +: (Optional, array of strings) + + A list of document IDs to pin at the top of the results, in the order provided. + +`documents` +: (Optional, array of objects) + + A list of objects specifying documents to pin. Each object must contain at least an `_id` field, and may also specify `_index` if pinning documents across multiple indices. + +`fallback` +: (Optional, retriever object) + + A retriever to use for retrieving the remaining documents after the pinned ones. + +Either `ids` or `documents` must be specified. + +### Example using `ids` [pinned-retriever-example-ids] + +```console +GET /restaurants/_search +{ + "retriever": { + "pinned": { + "ids": ["doc1", "doc2"], + "fallback": { + "standard": { + "query": { + "match": { + "title": "elasticsearch" + } + } + } + } + } + } +} +``` + +### Example using `documents` [pinned-retriever-example-documents] + +```console +GET /restaurants/_search +{ + "retriever": { + "pinned": { + "documents": [ + { "_id": "doc1", "_index": "my-index" }, + { "_id": "doc2" } + ], + "fallback": { + "standard": { + "query": { + "match": { + "title": "elasticsearch" + } + } + } + } + } + } +} +``` + ## Standard Retriever [standard-retriever] A standard retriever returns top documents from a traditional [query](/reference/query-languages/querydsl.md). From d906c965c248e329123f623dd9deddcd3301ad7a Mon Sep 17 00:00:00 2001 From: Mridula Date: Tue, 10 Jun 2025 18:39:50 +0100 Subject: [PATCH 41/46] reverted unnecessary change --- .../resources/rest-api-spec/test/linear/10_linear_retriever.yml | 1 - 1 file changed, 1 deletion(-) 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..61c8c82fb6046 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 @@ -316,7 +316,6 @@ 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": From a93c6bf8ad89bae3a9ae4b9d5199b8fd703eb107 Mon Sep 17 00:00:00 2001 From: Mridula Date: Wed, 11 Jun 2025 15:45:14 +0100 Subject: [PATCH 42/46] made the suggested changes --- .../elasticsearch/rest-apis/retrievers.md | 122 +++++++----------- 1 file changed, 49 insertions(+), 73 deletions(-) diff --git a/docs/reference/elasticsearch/rest-apis/retrievers.md b/docs/reference/elasticsearch/rest-apis/retrievers.md index f7423ae981387..4f99eb61b40e4 100644 --- a/docs/reference/elasticsearch/rest-apis/retrievers.md +++ b/docs/reference/elasticsearch/rest-apis/retrievers.md @@ -41,79 +41,12 @@ The following retrievers are available: `text_similarity_reranker` : A [retriever](#text-similarity-reranker-retriever) that enhances search results by re-ranking documents based on semantic similarity to a specified inference text, using a machine learning model. +`pinned` +: A [retriever](#pinned-retriever) that always places specified documents at the top of the results, with the remaining hits provided by a secondary retriever. + `rule` : A [retriever](#rule-retriever) that applies contextual [Searching with query rules](/reference/elasticsearch/rest-apis/searching-with-query-rules.md#query-rules) to pin or exclude documents for specific queries. -## Pinned Retriever [pinned-retriever] - -A `pinned` retriever returns top documents by always placing specific documents at the top of the results, in the order provided. This is useful for promoting certain documents for particular queries, regardless of their relevance score. The remaining results are filled by a fallback retriever. - -#### Parameters [pinned-retriever-parameters] - -`ids` -: (Optional, array of strings) - - A list of document IDs to pin at the top of the results, in the order provided. - -`documents` -: (Optional, array of objects) - - A list of objects specifying documents to pin. Each object must contain at least an `_id` field, and may also specify `_index` if pinning documents across multiple indices. - -`fallback` -: (Optional, retriever object) - - A retriever to use for retrieving the remaining documents after the pinned ones. - -Either `ids` or `documents` must be specified. - -### Example using `ids` [pinned-retriever-example-ids] - -```console -GET /restaurants/_search -{ - "retriever": { - "pinned": { - "ids": ["doc1", "doc2"], - "fallback": { - "standard": { - "query": { - "match": { - "title": "elasticsearch" - } - } - } - } - } - } -} -``` - -### Example using `documents` [pinned-retriever-example-documents] - -```console -GET /restaurants/_search -{ - "retriever": { - "pinned": { - "documents": [ - { "_id": "doc1", "_index": "my-index" }, - { "_id": "doc2" } - ], - "fallback": { - "standard": { - "query": { - "match": { - "title": "elasticsearch" - } - } - } - } - } - } -} -``` - ## Standard Retriever [standard-retriever] A standard retriever returns top documents from a traditional [query](/reference/query-languages/querydsl.md). @@ -991,7 +924,53 @@ GET movies/_search 1. The `rule` retriever is the outermost retriever, applying rules to the search results that were previously reranked using the `rrf` retriever. 2. The `rrf` retriever returns results from all of its sub-retrievers, and the output of the `rrf` retriever is used as input to the `rule` retriever. +## Pinned Retriever [pinned-retriever] + +A `pinned` retriever returns top documents by always placing specific documents at the top of the results, with the remaining hits provided by a secondary retriever. This retriever offers similar functionality to the [pinned query](/reference/query-languages/query-dsl/query-dsl-pinned-query.md), but works seamlessly with other retrievers. This is useful for promoting certain documents for particular queries, regardless of their relevance score. + +#### Parameters [pinned-retriever-parameters] + +`ids` +: (Optional, array of strings) + + A list of document IDs to pin at the top of the results, in the order provided. + +`documents` +: (Optional, array of objects) + + A list of objects specifying documents to pin. Each object must contain at least an `_id` field, and may also specify `_index` if pinning documents across multiple indices. + +`retriever` +: (Optional, retriever object) + A retriever (for example a `standard` retriever or a specialized retriever such as `rrf` retriever) used to retrieve the remaining documents after the pinned ones. + +Either `ids` or `documents` must be specified. + +### Example using `documents` [pinned-retriever-example-documents] + +```console +GET /restaurants/_search +{ + "retriever": { + "pinned": { + "documents": [ + { "_id": "doc1", "_index": "my-index" }, + { "_id": "doc2" } + ], + "retriever": { + "standard": { + "query": { + "match": { + "title": "elasticsearch" + } + } + } + } + } + } +} +``` ## Common usage guidelines [retriever-common-parameters] @@ -1016,6 +995,3 @@ When a retriever is specified as part of a search, the following elements are no * [`terminate_after`](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-search#request-body-search-terminate-after) * [`sort`](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-search#search-sort-param) * [`rescore`](/reference/elasticsearch/rest-apis/filter-search-results.md#rescore) use a [rescorer retriever](#rescorer-retriever) instead - - - From 4de5087fb91dda6906c30da0940079f3f85d9bc8 Mon Sep 17 00:00:00 2001 From: Mridula Date: Wed, 11 Jun 2025 19:05:46 +0100 Subject: [PATCH 43/46] Update retrievers.md --- docs/reference/elasticsearch/rest-apis/retrievers.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/elasticsearch/rest-apis/retrievers.md b/docs/reference/elasticsearch/rest-apis/retrievers.md index 4f99eb61b40e4..fb5432ebf5ea0 100644 --- a/docs/reference/elasticsearch/rest-apis/retrievers.md +++ b/docs/reference/elasticsearch/rest-apis/retrievers.md @@ -935,7 +935,7 @@ A `pinned` retriever returns top documents by always placing specific documents A list of document IDs to pin at the top of the results, in the order provided. -`documents` +`docs` : (Optional, array of objects) A list of objects specifying documents to pin. Each object must contain at least an `_id` field, and may also specify `_index` if pinning documents across multiple indices. @@ -945,16 +945,16 @@ A `pinned` retriever returns top documents by always placing specific documents A retriever (for example a `standard` retriever or a specialized retriever such as `rrf` retriever) used to retrieve the remaining documents after the pinned ones. -Either `ids` or `documents` must be specified. +Either `ids` or `docs` must be specified. -### Example using `documents` [pinned-retriever-example-documents] +### Example using `docs` [pinned-retriever-example-documents] ```console GET /restaurants/_search { "retriever": { "pinned": { - "documents": [ + "docs": [ { "_id": "doc1", "_index": "my-index" }, { "_id": "doc2" } ], From de3f1549bd537b870727deea473a231450016d60 Mon Sep 17 00:00:00 2001 From: Mridula Date: Tue, 17 Jun 2025 10:35:30 +0100 Subject: [PATCH 44/46] Update docs/reference/elasticsearch/rest-apis/retrievers.md Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- docs/reference/elasticsearch/rest-apis/retrievers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/elasticsearch/rest-apis/retrievers.md b/docs/reference/elasticsearch/rest-apis/retrievers.md index fb5432ebf5ea0..7c6fa85758b47 100644 --- a/docs/reference/elasticsearch/rest-apis/retrievers.md +++ b/docs/reference/elasticsearch/rest-apis/retrievers.md @@ -41,7 +41,7 @@ The following retrievers are available: `text_similarity_reranker` : A [retriever](#text-similarity-reranker-retriever) that enhances search results by re-ranking documents based on semantic similarity to a specified inference text, using a machine learning model. -`pinned` +`pinned` {applies_to}`stack: preview 9.1` : A [retriever](#pinned-retriever) that always places specified documents at the top of the results, with the remaining hits provided by a secondary retriever. `rule` From 3ee741cb86a0de07b65bbeaf3843626606f9a470 Mon Sep 17 00:00:00 2001 From: Mridula Date: Tue, 17 Jun 2025 10:35:41 +0100 Subject: [PATCH 45/46] Update docs/reference/elasticsearch/rest-apis/retrievers.md Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- docs/reference/elasticsearch/rest-apis/retrievers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/elasticsearch/rest-apis/retrievers.md b/docs/reference/elasticsearch/rest-apis/retrievers.md index 7c6fa85758b47..ff9a7e6efa667 100644 --- a/docs/reference/elasticsearch/rest-apis/retrievers.md +++ b/docs/reference/elasticsearch/rest-apis/retrievers.md @@ -925,6 +925,10 @@ GET movies/_search 2. The `rrf` retriever returns results from all of its sub-retrievers, and the output of the `rrf` retriever is used as input to the `rule` retriever. ## Pinned Retriever [pinned-retriever] +```yaml {applies_to} +stack: ga 9.1 +``` + A `pinned` retriever returns top documents by always placing specific documents at the top of the results, with the remaining hits provided by a secondary retriever. This retriever offers similar functionality to the [pinned query](/reference/query-languages/query-dsl/query-dsl-pinned-query.md), but works seamlessly with other retrievers. This is useful for promoting certain documents for particular queries, regardless of their relevance score. From 4e2e4f27aff790f52a3c4b6a59312dd7f389a26f Mon Sep 17 00:00:00 2001 From: Mridula Date: Tue, 17 Jun 2025 13:39:21 +0100 Subject: [PATCH 46/46] Update retrievers.md --- docs/reference/elasticsearch/rest-apis/retrievers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/elasticsearch/rest-apis/retrievers.md b/docs/reference/elasticsearch/rest-apis/retrievers.md index ff9a7e6efa667..cbcae05e42681 100644 --- a/docs/reference/elasticsearch/rest-apis/retrievers.md +++ b/docs/reference/elasticsearch/rest-apis/retrievers.md @@ -41,7 +41,7 @@ The following retrievers are available: `text_similarity_reranker` : A [retriever](#text-similarity-reranker-retriever) that enhances search results by re-ranking documents based on semantic similarity to a specified inference text, using a machine learning model. -`pinned` {applies_to}`stack: preview 9.1` +`pinned` {applies_to}`stack: GA 9.1` : A [retriever](#pinned-retriever) that always places specified documents at the top of the results, with the remaining hits provided by a secondary retriever. `rule`