diff --git a/TESTING.asciidoc b/TESTING.asciidoc index 96f94755a2758..2c205f9090ba8 100644 --- a/TESTING.asciidoc +++ b/TESTING.asciidoc @@ -551,13 +551,19 @@ When running `./gradlew check`, minimal bwc checks are also run against compatib ==== BWC Testing against a specific remote/branch -Sometimes a backward compatibility change spans two versions. A common case is a new functionality -that needs a BWC bridge in an unreleased versioned of a release branch (for example, 5.x). -To test the changes, you can instruct Gradle to build the BWC version from another remote/branch combination instead of -pulling the release branch from GitHub. You do so using the `bwc.remote` and `bwc.refspec.BRANCH` system properties: +Sometimes a backward compatibility change spans two versions. +A common case is a new functionality that needs a BWC bridge in an unreleased versioned of a release branch (for example, 5.x). +Another use case, since the introduction of serverless, is to test BWC against main in addition to the other released branches. +To do so, specify the `bwc.refspec` remote and branch to use for the BWC build as `origin/main`. +To test against main, you will also need to create a new version in link:./server/src/main/java/org/elasticsearch/Version.java[Version.java], +increment `elasticsearch` in link:./build-tools-internal/version.properties[version.properties], and hard-code the `project.version` for ml-cpp +in link:./x-pack/plugin/ml/build.gradle[ml/build.gradle]. + +In general, to test the changes, you can instruct Gradle to build the BWC version from another remote/branch combination instead of pulling the release branch from GitHub. +You do so using the `bwc.refspec.{VERSION}` system property: ------------------------------------------------- -./gradlew check -Dbwc.remote=${remote} -Dbwc.refspec.5.x=index_req_bwc_5.x +./gradlew check -Dtests.bwc.refspec.8.15=origin/main ------------------------------------------------- The branch needs to be available on the remote that the BWC makes of the diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/index/mapper/MapperServiceFactory.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/index/mapper/MapperServiceFactory.java index 9511a6bc01e08..70e9fe424e77b 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/index/mapper/MapperServiceFactory.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/index/mapper/MapperServiceFactory.java @@ -21,6 +21,7 @@ import org.elasticsearch.index.analysis.IndexAnalyzers; import org.elasticsearch.index.analysis.LowercaseNormalizer; import org.elasticsearch.index.analysis.NamedAnalyzer; +import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ProvidedIdFieldMapper; @@ -71,7 +72,8 @@ public static MapperService create(String mappings) { public T compile(Script script, ScriptContext scriptContext) { throw new UnsupportedOperationException(); } - } + }, + MapperMetrics.NOOP ); try { diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/search/QueryParserHelperBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/search/QueryParserHelperBenchmark.java index b6cbc3e7cce02..14f6fe6501a73 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/search/QueryParserHelperBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/search/QueryParserHelperBenchmark.java @@ -29,6 +29,7 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.index.fielddata.IndexFieldDataCache; +import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.ParsedDocument; @@ -154,7 +155,8 @@ protected SearchExecutionContext buildSearchExecutionContext() { null, () -> true, null, - Collections.emptyMap() + Collections.emptyMap(), + MapperMetrics.NOOP ); } @@ -186,7 +188,8 @@ protected final MapperService createMapperService(String mappings) { public T compile(Script script, ScriptContext scriptContext) { throw new UnsupportedOperationException(); } - } + }, + MapperMetrics.NOOP ); try { diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java index 24df3c4dab464..58b967d0a7722 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java @@ -60,6 +60,7 @@ import static org.gradle.api.JavaVersion.VERSION_20; import static org.gradle.api.JavaVersion.VERSION_21; import static org.gradle.api.JavaVersion.VERSION_22; +import static org.gradle.api.JavaVersion.VERSION_23; @CacheableTask public abstract class ThirdPartyAuditTask extends DefaultTask { @@ -336,8 +337,8 @@ private String runForbiddenAPIsCli() throws IOException { spec.setExecutable(javaHome.get() + "/bin/java"); } spec.classpath(getForbiddenAPIsClasspath(), classpath); - // Enable explicitly for each release as appropriate. Just JDK 20/21/22 for now, and just the vector module. - if (isJavaVersion(VERSION_20) || isJavaVersion(VERSION_21) || isJavaVersion(VERSION_22)) { + // Enable explicitly for each release as appropriate. Just JDK 20/21/22/23 for now, and just the vector module. + if (isJavaVersion(VERSION_20) || isJavaVersion(VERSION_21) || isJavaVersion(VERSION_22) || isJavaVersion(VERSION_23)) { spec.jvmArgs("--add-modules", "jdk.incubator.vector"); } spec.jvmArgs("-Xmx1g"); diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolver.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolver.java index 0270ee22ca8c5..89a40711c9a19 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolver.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolver.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.compress.utils.Lists; import org.gradle.jvm.toolchain.JavaLanguageVersion; import org.gradle.jvm.toolchain.JavaToolchainDownload; import org.gradle.jvm.toolchain.JavaToolchainRequest; @@ -21,17 +20,17 @@ import java.io.IOException; import java.net.URI; import java.net.URL; -import java.util.Comparator; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.StreamSupport; import static org.gradle.jvm.toolchain.JavaToolchainDownload.fromUri; public abstract class AdoptiumJdkToolchainResolver extends AbstractCustomJavaToolchainResolver { // package protected for better testing - final Map> CACHED_SEMVERS = new ConcurrentHashMap<>(); + final Map> CACHED_RELEASES = new ConcurrentHashMap<>(); @Override public Optional resolve(JavaToolchainRequest request) { @@ -39,7 +38,7 @@ public Optional resolve(JavaToolchainRequest request) { return Optional.empty(); } AdoptiumVersionRequest versionRequestKey = toVersionRequest(request); - Optional versionInfo = CACHED_SEMVERS.computeIfAbsent( + Optional versionInfo = CACHED_RELEASES.computeIfAbsent( versionRequestKey, (r) -> resolveAvailableVersion(versionRequestKey) ); @@ -54,12 +53,12 @@ private AdoptiumVersionRequest toVersionRequest(JavaToolchainRequest request) { return new AdoptiumVersionRequest(platform, arch, javaLanguageVersion); } - private Optional resolveAvailableVersion(AdoptiumVersionRequest requestKey) { + private Optional resolveAvailableVersion(AdoptiumVersionRequest requestKey) { ObjectMapper mapper = new ObjectMapper(); try { int languageVersion = requestKey.languageVersion.asInt(); URL source = new URL( - "https://api.adoptium.net/v3/info/release_versions?architecture=" + "https://api.adoptium.net/v3/info/release_names?architecture=" + requestKey.arch + "&image_type=jdk&os=" + requestKey.platform @@ -71,14 +70,8 @@ private Optional resolveAvailableVersion(AdoptiumVersionReq + ")" ); JsonNode jsonNode = mapper.readTree(source); - JsonNode versionsNode = jsonNode.get("versions"); - return Optional.of( - Lists.newArrayList(versionsNode.iterator()) - .stream() - .map(this::toVersionInfo) - .max(Comparator.comparing(AdoptiumVersionInfo::semver)) - .get() - ); + JsonNode versionsNode = jsonNode.get("releases"); + return StreamSupport.stream(versionsNode.spliterator(), false).map(JsonNode::textValue).findFirst(); } catch (FileNotFoundException e) { // request combo not supported (e.g. aarch64 + windows return Optional.empty(); @@ -87,21 +80,10 @@ private Optional resolveAvailableVersion(AdoptiumVersionReq } } - private AdoptiumVersionInfo toVersionInfo(JsonNode node) { - return new AdoptiumVersionInfo( - node.get("build").asInt(), - node.get("major").asInt(), - node.get("minor").asInt(), - node.get("openjdk_version").asText(), - node.get("security").asInt(), - node.get("semver").asText() - ); - } - - private URI resolveDownloadURI(AdoptiumVersionRequest request, AdoptiumVersionInfo versionInfo) { + private URI resolveDownloadURI(AdoptiumVersionRequest request, String version) { return URI.create( - "https://api.adoptium.net/v3/binary/version/jdk-" - + versionInfo.semver + "https://api.adoptium.net/v3/binary/version/" + + version + "/" + request.platform + "/" @@ -118,7 +100,5 @@ private boolean requestIsSupported(JavaToolchainRequest request) { return anyVendorOr(request.getJavaToolchainSpec().getVendor().get(), JvmVendorSpec.ADOPTIUM); } - record AdoptiumVersionInfo(int build, int major, int minor, String openjdkVersion, int security, String semver) {} - record AdoptiumVersionRequest(String platform, String arch, JavaLanguageVersion languageVersion) {} } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/OracleOpenJdkToolchainResolver.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/OracleOpenJdkToolchainResolver.java index 818cb040c172e..162895fd486cf 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/OracleOpenJdkToolchainResolver.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/toolchain/OracleOpenJdkToolchainResolver.java @@ -39,11 +39,7 @@ record JdkBuild(JavaLanguageVersion languageVersion, String version, String buil ); // package private so it can be replaced by tests - List builds = List.of( - getBundledJdkBuild(), - // 22 release candidate - new JdkBuild(JavaLanguageVersion.of(22), "22", "36", "830ec9fcccef480bb3e73fb7ecafe059") - ); + List builds = List.of(getBundledJdkBuild()); private JdkBuild getBundledJdkBuild() { String bundledJdkVersion = VersionProperties.getBundledJdkVersion(); diff --git a/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolverSpec.groovy b/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolverSpec.groovy index 6383d577f027f..fe4a644ddfc1d 100644 --- a/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolverSpec.groovy +++ b/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/toolchain/AdoptiumJdkToolchainResolverSpec.groovy @@ -11,7 +11,6 @@ package org.elasticsearch.gradle.internal.toolchain import org.gradle.api.services.BuildServiceParameters import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaToolchainResolver -import org.gradle.platform.OperatingSystem import static org.elasticsearch.gradle.internal.toolchain.AbstractCustomJavaToolchainResolver.toArchString import static org.elasticsearch.gradle.internal.toolchain.AbstractCustomJavaToolchainResolver.toOsString @@ -38,12 +37,7 @@ class AdoptiumJdkToolchainResolverSpec extends AbstractToolchainResolverSpec { toOsString(it[2], it[1]), toArchString(it[3]), languageVersion); - resolver.CACHED_SEMVERS.put(request, Optional.of(new AdoptiumJdkToolchainResolver.AdoptiumVersionInfo(languageVersion.asInt(), - 1, - 1, - "" + languageVersion.asInt() + ".1.1.1+37", - 0, "" + languageVersion.asInt() + ".1.1.1+37.1" - ))) + resolver.CACHED_RELEASES.put(request, Optional.of('jdk-' + languageVersion.asInt() + '.1.1.1+37.1')) } return resolver diff --git a/build-tools-internal/version.properties b/build-tools-internal/version.properties index 044f6c07c756e..a2744e89174b2 100644 --- a/build-tools-internal/version.properties +++ b/build-tools-internal/version.properties @@ -2,7 +2,7 @@ elasticsearch = 8.15.0 lucene = 9.10.0 bundled_jdk_vendor = openjdk -bundled_jdk = 21.0.2+13@f2283984656d49d69e91c558476027ac +bundled_jdk = 22.0.1+8@c7ec1332f7bb44aeba2eb341ae18aca4 # optional dependencies spatial4j = 0.7 jts = 1.15.0 diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java b/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java index 31e1cb882305a..999f27a646b1f 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java @@ -1107,11 +1107,11 @@ private void logFileContents(String description, Path from, boolean tailLogs) { return; } - boolean foundNettyLeaks = false; + boolean foundLeaks = false; for (String logLine : errorsAndWarnings.keySet()) { - if (logLine.contains("ResourceLeakDetector]")) { + if (logLine.contains("ResourceLeakDetector") || logLine.contains("LeakTracker")) { tailLogs = true; - foundNettyLeaks = true; + foundLeaks = true; break; } } @@ -1140,8 +1140,8 @@ private void logFileContents(String description, Path from, boolean tailLogs) { }); } } - if (foundNettyLeaks) { - throw new TestClustersException("Found Netty ByteBuf leaks in node logs."); + if (foundLeaks) { + throw new TestClustersException("Found resource leaks in node logs."); } } diff --git a/client/test/build.gradle b/client/test/build.gradle index d9a10a9c6ffdc..8d457948b91b4 100644 --- a/client/test/build.gradle +++ b/client/test/build.gradle @@ -27,9 +27,9 @@ dependencies { api "org.hamcrest:hamcrest:${versions.hamcrest}" // mockito - api 'org.mockito:mockito-core:5.9.0' - api 'org.mockito:mockito-subclass:5.9.0' - api 'net.bytebuddy:byte-buddy:1.14.11' + api 'org.mockito:mockito-core:5.11.0' + api 'org.mockito:mockito-subclass:5.11.0' + api 'net.bytebuddy:byte-buddy:1.14.12' api 'org.objenesis:objenesis:3.3' } diff --git a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java index 8cfe9a1f03914..f853304bcdf90 100644 --- a/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java +++ b/distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java @@ -27,62 +27,64 @@ static List systemJvmOptions(Settings nodeSettings, final Map sysprops) { diff --git a/docs/changelog/106636.yaml b/docs/changelog/106636.yaml deleted file mode 100644 index e110d98ca577d..0000000000000 --- a/docs/changelog/106636.yaml +++ /dev/null @@ -1,5 +0,0 @@ -pr: 106636 -summary: "ESQL: Add OPTIONS clause to FROM command" -area: ES|QL -type: enhancement -issues: [] diff --git a/docs/changelog/108409.yaml b/docs/changelog/108409.yaml new file mode 100644 index 0000000000000..6cff86cf93930 --- /dev/null +++ b/docs/changelog/108409.yaml @@ -0,0 +1,6 @@ +pr: 108409 +summary: Support multiple associated groups for TopN +area: Application +type: enhancement +issues: + - 108018 diff --git a/docs/changelog/108574.yaml b/docs/changelog/108574.yaml new file mode 100644 index 0000000000000..b3c957721e01e --- /dev/null +++ b/docs/changelog/108574.yaml @@ -0,0 +1,5 @@ +pr: 108574 +summary: "[ESQL] CBRT function" +area: ES|QL +type: enhancement +issues: [] diff --git a/docs/changelog/108600.yaml b/docs/changelog/108600.yaml new file mode 100644 index 0000000000000..59177bf34114c --- /dev/null +++ b/docs/changelog/108600.yaml @@ -0,0 +1,15 @@ +pr: 108600 +summary: "Prevent DLS/FLS if `replication` is assigned" +area: Security +type: breaking +issues: [ ] +breaking: + title: "Prevent DLS/FLS if `replication` is assigned" + area: REST API + details: For cross-cluster API keys, {es} no longer allows specifying document-level security (DLS) + or field-level security (FLS) in the `search` field, if `replication` is also specified. + {es} likewise blocks the use of any existing cross-cluster API keys that meet this condition. + impact: Remove any document-level security (DLS) or field-level security (FLS) definitions from the `search` field + for cross-cluster API keys that also have a `replication` field, or create two separate cross-cluster API keys, + one for search and one for replication. + notable: false diff --git a/docs/changelog/108602.yaml b/docs/changelog/108602.yaml new file mode 100644 index 0000000000000..d544c89980123 --- /dev/null +++ b/docs/changelog/108602.yaml @@ -0,0 +1,5 @@ +pr: 108602 +summary: "[Inference API] Extract optional long instead of integer in `RateLimitSettings#of`" +area: Machine Learning +type: bug +issues: [] diff --git a/docs/changelog/108639.yaml b/docs/changelog/108639.yaml new file mode 100644 index 0000000000000..586270c3c761c --- /dev/null +++ b/docs/changelog/108639.yaml @@ -0,0 +1,28 @@ +pr: 108639 +summary: Add support for the 'Domain' database to the geoip processor +area: Ingest Node +type: enhancement +issues: [] +highlight: + title: Add support for the 'Domain' database to the geoip processor + body: |- + Follow on to #107287 and #107377 + + Adds support for the ['GeoIP2 + Domain'](https://dev.maxmind.com/geoip/docs/databases/domain) database + from MaxMind to the `geoip` processor. + + The `geoip` processor will automatically download the [various + 'GeoLite2' + databases](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data), + but the 'GeoIP2 Domain' database is not a 'GeoLite2' database -- it's a + commercial database available to those with a suitable license from + MaxMind. + + The support that is being added for it in this PR is in line with the + support that we already have for MaxMind's 'GeoIP2 City' and 'GeoIP2 + Country' databases -- that is, one would need to arrange their own + download management via some custom endpoint or otherwise arrange for + the relevant file(s) to be in the `$ES_CONFIG/ingest-geoip` directory on + the nodes of the cluster. + notable: true diff --git a/docs/changelog/108643.yaml b/docs/changelog/108643.yaml new file mode 100644 index 0000000000000..f71a943673326 --- /dev/null +++ b/docs/changelog/108643.yaml @@ -0,0 +1,6 @@ +pr: 108643 +summary: Use `scheduleUnlessShuttingDown` in `LeaderChecker` +area: Cluster Coordination +type: bug +issues: + - 108642 diff --git a/docs/changelog/108651.yaml b/docs/changelog/108651.yaml new file mode 100644 index 0000000000000..e629c114dac51 --- /dev/null +++ b/docs/changelog/108651.yaml @@ -0,0 +1,29 @@ +pr: 108651 +summary: Add support for the 'ISP' database to the geoip processor +area: Ingest Node +type: enhancement +issues: [] +highlight: + title: Add support for the 'ISP' database to the geoip processor + body: |- + Follow on to https://github.com/elastic/elasticsearch/pull/107287, + https://github.com/elastic/elasticsearch/pull/107377, and + https://github.com/elastic/elasticsearch/pull/108639 + + Adds support for the ['GeoIP2 + ISP'](https://dev.maxmind.com/geoip/docs/databases/isp) database from + MaxMind to the geoip processor. + + The geoip processor will automatically download the [various 'GeoLite2' + databases](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data), + but the 'GeoIP2 ISP' database is not a 'GeoLite2' database -- it's a + commercial database available to those with a suitable license from + MaxMind. + + The support that is being added for it in this PR is in line with the + support that we already have for MaxMind's 'GeoIP2 City' and 'GeoIP2 + Country' databases -- that is, one would need to arrange their own + download management via some custom endpoint or otherwise arrange for + the relevant file(s) to be in the $ES_CONFIG/ingest-geoip directory on + the nodes of the cluster. + notable: true diff --git a/docs/changelog/108654.yaml b/docs/changelog/108654.yaml new file mode 100644 index 0000000000000..9afae6a19ca80 --- /dev/null +++ b/docs/changelog/108654.yaml @@ -0,0 +1,5 @@ +pr: 108654 +summary: Update bundled JDK to Java 22 (again) +area: Packaging +type: upgrade +issues: [] diff --git a/docs/changelog/108672.yaml b/docs/changelog/108672.yaml new file mode 100644 index 0000000000000..e1261fcf6f232 --- /dev/null +++ b/docs/changelog/108672.yaml @@ -0,0 +1,5 @@ +pr: 108672 +summary: Add bounds checking to parsing ISO8601 timezone offset values +area: Infra/Core +type: bug +issues: [] diff --git a/docs/changelog/108683.yaml b/docs/changelog/108683.yaml new file mode 100644 index 0000000000000..ad796fb9b25c7 --- /dev/null +++ b/docs/changelog/108683.yaml @@ -0,0 +1,28 @@ +pr: 108683 +summary: Add support for the 'Connection Type' database to the geoip processor +area: Ingest Node +type: enhancement +issues: [] +highlight: + title: Add support for the 'Connection Type' database to the geoip processor + body: |- + Follow on to #107287, #107377, #108639, and #108651 + + Adds support for the ['GeoIP2 Connection + Type'](https://dev.maxmind.com/geoip/docs/databases/connection-type) + database from MaxMind to the `geoip` processor. + + The `geoip` processor will automatically download the [various + 'GeoLite2' + databases](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data), + but the 'GeoIP2 Connection Type' database is not a 'GeoLite2' database + -- it's a commercial database available to those with a suitable license + from MaxMind. + + The support that is being added for it in this PR is in line with the + support that we already have for MaxMind's 'GeoIP2 City' and 'GeoIP2 + Country' databases -- that is, one would need to arrange their own + download management via some custom endpoint or otherwise arrange for + the relevant file(s) to be in the `$ES_CONFIG/ingest-geoip` directory on + the nodes of the cluster. + notable: true diff --git a/docs/changelog/108687.yaml b/docs/changelog/108687.yaml new file mode 100644 index 0000000000000..771516d551567 --- /dev/null +++ b/docs/changelog/108687.yaml @@ -0,0 +1,5 @@ +pr: 108687 +summary: Adding `user_type` support for the enterprise database for the geoip processor +area: Ingest Node +type: enhancement +issues: [] diff --git a/docs/reference/cluster/get-settings.asciidoc b/docs/reference/cluster/get-settings.asciidoc index 5a9fe81df61c7..32c186e4ef24c 100644 --- a/docs/reference/cluster/get-settings.asciidoc +++ b/docs/reference/cluster/get-settings.asciidoc @@ -40,4 +40,4 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=flat-settings] (Optional, Boolean) If `true`, returns default cluster settings from the local node. Defaults to `false`. -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] diff --git a/docs/reference/cluster/nodes-info.asciidoc b/docs/reference/cluster/nodes-info.asciidoc index 8ff7da3a16ad1..6f1d769e696c5 100644 --- a/docs/reference/cluster/nodes-info.asciidoc +++ b/docs/reference/cluster/nodes-info.asciidoc @@ -184,7 +184,7 @@ running process: include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=flat-settings] -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeout-nodes-request] [[cluster-nodes-info-api-example]] diff --git a/docs/reference/cluster/nodes-stats.asciidoc b/docs/reference/cluster/nodes-stats.asciidoc index d0e4188ce74ed..bccef4bb613b3 100644 --- a/docs/reference/cluster/nodes-stats.asciidoc +++ b/docs/reference/cluster/nodes-stats.asciidoc @@ -147,7 +147,7 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=level] (Optional, string) A comma-separated list of document types for the `indexing` index metric. -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeout-nodes-request] include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=include-segment-file-sizes] diff --git a/docs/reference/cluster/nodes-usage.asciidoc b/docs/reference/cluster/nodes-usage.asciidoc index 6c53919bcfbbc..486edf67bba87 100644 --- a/docs/reference/cluster/nodes-usage.asciidoc +++ b/docs/reference/cluster/nodes-usage.asciidoc @@ -54,7 +54,7 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=node-id] [[cluster-nodes-usage-api-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeout-nodes-request] [[cluster-nodes-usage-api-example]] diff --git a/docs/reference/cluster/tasks.asciidoc b/docs/reference/cluster/tasks.asciidoc index 0ffd700957506..4b32d5f1b903a 100644 --- a/docs/reference/cluster/tasks.asciidoc +++ b/docs/reference/cluster/tasks.asciidoc @@ -48,7 +48,11 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=nodes] include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=parent-task-id] -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +`timeout`:: +(Optional, <>) +Period to wait for each node to respond. If a node does not respond before its +timeout expires, the response does not include its information. However, timed out +nodes are included in the response's `node_failures` property. Defaults to `30s`. `wait_for_completion`:: (Optional, Boolean) If `true`, the request blocks until all found tasks are complete. diff --git a/docs/reference/connector/apis/update-connector-filtering-api.asciidoc b/docs/reference/connector/apis/update-connector-filtering-api.asciidoc index f864b68f65395..7ae80276d3151 100644 --- a/docs/reference/connector/apis/update-connector-filtering-api.asciidoc +++ b/docs/reference/connector/apis/update-connector-filtering-api.asciidoc @@ -6,7 +6,7 @@ beta::[] -Updates the draft `filtering` configuration of a connector and marks the draft validation state as `edited`. The filtering configuration can be activated once validated by the Elastic connector service. +Updates the draft `filtering` configuration of a connector and marks the draft validation state as `edited`. The filtering draft is activated once validated by the running Elastic connector service. The filtering property is used to configure sync rules (both basic and advanced) for a connector. Learn more in the {enterprise-search-ref}/sync-rules.html[sync rules documentation]. @@ -15,14 +15,13 @@ The filtering property is used to configure sync rules (both basic and advanced) `PUT _connector//_filtering` -`PUT _connector//_filtering/_activate` - [[update-connector-filtering-api-prereq]] ==== {api-prereq-title} * To sync data using self-managed connectors, you need to deploy the {enterprise-search-ref}/build-connector.html[Elastic connector service] on your own infrastructure. This service runs automatically on Elastic Cloud for native connectors. * The `connector_id` parameter should reference an existing connector. -* To activate filtering rules, the `draft.validation.state` must be `valid`. +* Filtering draft is activated once validated by the running Elastic connector service, the `draft.validation.state` must be `valid`. +* If, after a validation attempt, the `draft.validation.state` equals to `invalid`, inspect `draft.validation.errors` and fix any issues. [[update-connector-filtering-api-path-params]] ==== {api-path-parms-title} @@ -185,20 +184,4 @@ PUT _connector/my-sql-connector/_filtering/_validation Note, you can also update draft `rules` and `advanced_snippet` in a single request. -Once the draft is updated, its validation state is set to `edited`. The connector service will then validate the rules and report the validation state as either `invalid` or `valid`. If the state is `valid`, the draft filtering can be activated with: - - -[source,console] ----- -PUT _connector/my-sql-connector/_filtering/_activate ----- -// TEST[continued] - -[source,console-result] ----- -{ - "result": "updated" -} ----- - -Once filtering rules are activated, they will be applied to all subsequent full or incremental syncs. +Once the draft is updated, its validation state is set to `edited`. The connector service will then validate the rules and report the validation state as either `invalid` or `valid`. If the state is `valid`, the draft filtering will be activated by the running Elastic connector service. diff --git a/docs/reference/data-streams/lifecycle/apis/explain-lifecycle.asciidoc b/docs/reference/data-streams/lifecycle/apis/explain-lifecycle.asciidoc index b739751ca5b02..7968bb78939e8 100644 --- a/docs/reference/data-streams/lifecycle/apis/explain-lifecycle.asciidoc +++ b/docs/reference/data-streams/lifecycle/apis/explain-lifecycle.asciidoc @@ -38,7 +38,7 @@ execution. (Optional, Boolean) Includes default configurations related to the lifecycle of the target. Defaults to `false`. -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] [[data-streams-explain-lifecycle-example]] ==== {api-examples-title} diff --git a/docs/reference/esql/esql-index-options.asciidoc b/docs/reference/esql/esql-index-options.asciidoc deleted file mode 100644 index 721461bd96719..0000000000000 --- a/docs/reference/esql/esql-index-options.asciidoc +++ /dev/null @@ -1,52 +0,0 @@ -[[esql-index-options]] -=== {esql} index options - -++++ -Index options -++++ - -The `OPTIONS` directive of the <> command allows configuring -the way {esql} accesses the data to be queried. The argument passed to this -directive is a comma-separated list of option name-value pairs, with the option -name and the corresponding value double-quoted. - -[source,esql] ----- -FROM index_pattern [OPTIONS "option1"="value1"[,...[,"optionN"="valueN"]]] ----- - -These options can only be provided as part of a <> command, -and they apply to all the indices provided or matched by an index pattern. - -The option names and their values are the same as used by the -<>, however note that the default -values may differ. - -The currently supported options are: - -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=allow-no-indices] -+ -Defaults to `true`. - -// unlike "allow-no-indices", "index-ignore-unavailable" includes a default -// in common-parms.asciidoc, which is different from QL's -- we need to -// provide the full text here. -`ignore_unavailable`:: -(Optional, Boolean) If `false`, the request returns an error if it targets a -missing or closed index. -+ -Defaults to `true`. - -include::{es-ref-dir}/search/search.asciidoc[tag=search-preference] - -*Examples* - -[source.merge.styled,esql] ----- -include::{esql-specs}/from.csv-spec[tag=convertFromDatetimeWithOptions] ----- -[%header.monospaced.styled,format=dsv,separator=|] -|=== -include::{esql-specs}/from.csv-spec[tag=convertFromDatetimeWithOptions-result] -|=== - diff --git a/docs/reference/esql/esql-language.asciidoc b/docs/reference/esql/esql-language.asciidoc index 77f5e79753fdd..a7c0e5e01a867 100644 --- a/docs/reference/esql/esql-language.asciidoc +++ b/docs/reference/esql/esql-language.asciidoc @@ -10,16 +10,16 @@ Detailed reference documentation for the {esql} language: * <> * <> * <> -* <> * <> * <> * <> +* <> include::esql-syntax.asciidoc[] include::esql-commands.asciidoc[] include::esql-functions-operators.asciidoc[] include::metadata-fields.asciidoc[] -include::esql-index-options.asciidoc[] include::multivalued-fields.asciidoc[] include::esql-process-data-with-dissect-grok.asciidoc[] include::esql-enrich-data.asciidoc[] +include::implicit-casting.asciidoc[] diff --git a/docs/reference/esql/functions/description/cbrt.asciidoc b/docs/reference/esql/functions/description/cbrt.asciidoc new file mode 100644 index 0000000000000..836dec8a87d69 --- /dev/null +++ b/docs/reference/esql/functions/description/cbrt.asciidoc @@ -0,0 +1,5 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Description* + +Returns the cube root of a number. The input can be any numeric value, the return value is always a double. Cube roots of infinities are null. diff --git a/docs/reference/esql/functions/description/sqrt.asciidoc b/docs/reference/esql/functions/description/sqrt.asciidoc index 61e4f9b64fcd1..b9f354a33541f 100644 --- a/docs/reference/esql/functions/description/sqrt.asciidoc +++ b/docs/reference/esql/functions/description/sqrt.asciidoc @@ -2,4 +2,4 @@ *Description* -Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinites are null. +Returns the square root of a number. The input can be any numeric value, the return value is always a double. Square roots of negative numbers and infinities are null. diff --git a/docs/reference/esql/functions/examples/cbrt.asciidoc b/docs/reference/esql/functions/examples/cbrt.asciidoc new file mode 100644 index 0000000000000..56f1ef0a819e0 --- /dev/null +++ b/docs/reference/esql/functions/examples/cbrt.asciidoc @@ -0,0 +1,13 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Example* + +[source.merge.styled,esql] +---- +include::{esql-specs}/math.csv-spec[tag=cbrt] +---- +[%header.monospaced.styled,format=dsv,separator=|] +|=== +include::{esql-specs}/math.csv-spec[tag=cbrt-result] +|=== + diff --git a/docs/reference/esql/functions/kibana/definition/cbrt.json b/docs/reference/esql/functions/kibana/definition/cbrt.json new file mode 100644 index 0000000000000..600174e17ca0c --- /dev/null +++ b/docs/reference/esql/functions/kibana/definition/cbrt.json @@ -0,0 +1,59 @@ +{ + "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", + "type" : "eval", + "name" : "cbrt", + "description" : "Returns the cube root of a number. The input can be any numeric value, the return value is always a double.\nCube roots of infinities are null.", + "signatures" : [ + { + "params" : [ + { + "name" : "number", + "type" : "double", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "number", + "type" : "integer", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "number", + "type" : "long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + }, + { + "params" : [ + { + "name" : "number", + "type" : "unsigned_long", + "optional" : false, + "description" : "Numeric expression. If `null`, the function returns `null`." + } + ], + "variadic" : false, + "returnType" : "double" + } + ], + "examples" : [ + "ROW d = 1000.0\n| EVAL c = cbrt(d)" + ] +} diff --git a/docs/reference/esql/functions/kibana/definition/sqrt.json b/docs/reference/esql/functions/kibana/definition/sqrt.json index e990049a9ce67..7d9111036402d 100644 --- a/docs/reference/esql/functions/kibana/definition/sqrt.json +++ b/docs/reference/esql/functions/kibana/definition/sqrt.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.", "type" : "eval", "name" : "sqrt", - "description" : "Returns the square root of a number. The input can be any numeric value, the return value is always a double.\nSquare roots of negative numbers and infinites are null.", + "description" : "Returns the square root of a number. The input can be any numeric value, the return value is always a double.\nSquare roots of negative numbers and infinities are null.", "signatures" : [ { "params" : [ diff --git a/docs/reference/esql/functions/kibana/docs/cbrt.md b/docs/reference/esql/functions/kibana/docs/cbrt.md new file mode 100644 index 0000000000000..50cdad02818e8 --- /dev/null +++ b/docs/reference/esql/functions/kibana/docs/cbrt.md @@ -0,0 +1,12 @@ + + +### CBRT +Returns the cube root of a number. The input can be any numeric value, the return value is always a double. +Cube roots of infinities are null. + +``` +ROW d = 1000.0 +| EVAL c = cbrt(d) +``` diff --git a/docs/reference/esql/functions/kibana/docs/sqrt.md b/docs/reference/esql/functions/kibana/docs/sqrt.md index 264abe53921c4..fccec95a4884d 100644 --- a/docs/reference/esql/functions/kibana/docs/sqrt.md +++ b/docs/reference/esql/functions/kibana/docs/sqrt.md @@ -4,7 +4,7 @@ This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../READ ### SQRT Returns the square root of a number. The input can be any numeric value, the return value is always a double. -Square roots of negative numbers and infinites are null. +Square roots of negative numbers and infinities are null. ``` ROW d = 100.0 diff --git a/docs/reference/esql/functions/layout/cbrt.asciidoc b/docs/reference/esql/functions/layout/cbrt.asciidoc new file mode 100644 index 0000000000000..18106f0e6ca35 --- /dev/null +++ b/docs/reference/esql/functions/layout/cbrt.asciidoc @@ -0,0 +1,15 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +[discrete] +[[esql-cbrt]] +=== `CBRT` + +*Syntax* + +[.text-center] +image::esql/functions/signature/cbrt.svg[Embedded,opts=inline] + +include::../parameters/cbrt.asciidoc[] +include::../description/cbrt.asciidoc[] +include::../types/cbrt.asciidoc[] +include::../examples/cbrt.asciidoc[] diff --git a/docs/reference/esql/functions/math-functions.asciidoc b/docs/reference/esql/functions/math-functions.asciidoc index 9aa5cd2db1927..db907c8d54061 100644 --- a/docs/reference/esql/functions/math-functions.asciidoc +++ b/docs/reference/esql/functions/math-functions.asciidoc @@ -13,6 +13,7 @@ * <> * <> * <> +* <> * <> * <> * <> @@ -37,6 +38,7 @@ include::layout/acos.asciidoc[] include::layout/asin.asciidoc[] include::layout/atan.asciidoc[] include::layout/atan2.asciidoc[] +include::layout/cbrt.asciidoc[] include::layout/ceil.asciidoc[] include::layout/cos.asciidoc[] include::layout/cosh.asciidoc[] diff --git a/docs/reference/esql/functions/parameters/cbrt.asciidoc b/docs/reference/esql/functions/parameters/cbrt.asciidoc new file mode 100644 index 0000000000000..65013f4c21265 --- /dev/null +++ b/docs/reference/esql/functions/parameters/cbrt.asciidoc @@ -0,0 +1,6 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Parameters* + +`number`:: +Numeric expression. If `null`, the function returns `null`. diff --git a/docs/reference/esql/functions/signature/cbrt.svg b/docs/reference/esql/functions/signature/cbrt.svg new file mode 100644 index 0000000000000..ba96c276caaa0 --- /dev/null +++ b/docs/reference/esql/functions/signature/cbrt.svg @@ -0,0 +1 @@ +CBRT(number) \ No newline at end of file diff --git a/docs/reference/esql/functions/type-conversion-functions.asciidoc b/docs/reference/esql/functions/type-conversion-functions.asciidoc index 2fec7f40bde8b..96c29a776bc2b 100644 --- a/docs/reference/esql/functions/type-conversion-functions.asciidoc +++ b/docs/reference/esql/functions/type-conversion-functions.asciidoc @@ -5,6 +5,11 @@ Type conversion functions ++++ +[TIP] +==== +{esql} supports implicit casting from string literals to certain data types. Refer to <> for details. +==== + {esql} supports these type conversion functions: // tag::type_list[] diff --git a/docs/reference/esql/functions/types/cbrt.asciidoc b/docs/reference/esql/functions/types/cbrt.asciidoc new file mode 100644 index 0000000000000..7cda278abdb56 --- /dev/null +++ b/docs/reference/esql/functions/types/cbrt.asciidoc @@ -0,0 +1,12 @@ +// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + +*Supported types* + +[%header.monospaced.styled,format=dsv,separator=|] +|=== +number | result +double | double +integer | double +long | double +unsigned_long | double +|=== diff --git a/docs/reference/esql/implicit-casting.asciidoc b/docs/reference/esql/implicit-casting.asciidoc new file mode 100644 index 0000000000000..f0c0aa3d82063 --- /dev/null +++ b/docs/reference/esql/implicit-casting.asciidoc @@ -0,0 +1,53 @@ +[[esql-implicit-casting]] +=== {esql} implicit casting + +++++ +Implicit casting +++++ + +Often users will input `datetime`, `ip`, `version`, or geospatial objects as simple strings in their queries for use in predicates, functions, or expressions. {esql} provides <> to explicitly convert these strings into the desired data types. + +Without implicit casting users must explicitly code these `to_X` functions in their queries, when string literals don't match the target data types they are assigned or compared to. Here is an example of using `to_datetime` to explicitly perform a data type conversion. + +[source.merge.styled,esql] +---- +FROM employees +| EVAL dd_ns1=date_diff("day", to_datetime("2023-12-02T11:00:00.00Z"), birth_date) +| SORT emp_no +| KEEP dd_ns1 +| LIMIT 1 +---- + +Implicit casting improves usability, by automatically converting string literals to the target data type. This is most useful when the target data type is `datetime`, `ip`, `version` or a geo spatial. It is natural to specify these as a string in queries. + +The first query can be coded without calling the `to_datetime` function, as follows: + +[source.merge.styled,esql] +---- +FROM employees +| EVAL dd_ns1=date_diff("day", "2023-12-02T11:00:00.00Z", birth_date) +| SORT emp_no +| KEEP dd_ns1 +| LIMIT 1 +---- + +[float] +=== Implicit casting support + +The following table details which {esql} operations support implicit casting for different data types. + +[%header.monospaced.styled,format=dsv,separator=|] +|=== +||ScalarFunction|BinaryComparison|ArithmeticOperation|InListPredicate|AggregateFunction +|DATETIME|Y|Y|Y|Y|N +|DOUBLE|Y|N|N|N|N +|LONG|Y|N|N|N|N +|INTEGER|Y|N|N|N|N +|IP|Y|Y|Y|Y|N +|VERSION|Y|Y|Y|Y|N +|GEO_POINT|Y|N|N|N|N +|GEO_SHAPE|Y|N|N|N|N +|CARTESIAN_POINT|Y|N|N|N|N +|CARTESIAN_SHAPE|Y|N|N|N|N +|BOOLEAN|Y|Y|Y|Y|N +|=== diff --git a/docs/reference/esql/source-commands/from.asciidoc b/docs/reference/esql/source-commands/from.asciidoc index 427562a8c0dbb..d81c46530e089 100644 --- a/docs/reference/esql/source-commands/from.asciidoc +++ b/docs/reference/esql/source-commands/from.asciidoc @@ -6,7 +6,7 @@ [source,esql] ---- -FROM index_pattern [METADATA fields] [OPTIONS options] +FROM index_pattern [METADATA fields] ---- *Parameters* @@ -17,10 +17,6 @@ A list of indices, data streams or aliases. Supports wildcards and date math. `fields`:: A comma-separated list of <> to retrieve. -`options`:: -A comma-separated list of <> to configure -data access. - *Description* The `FROM` source command returns a table with data from a data stream, index, @@ -86,11 +82,3 @@ Use the optional `METADATA` directive to enable <>. -This directive must follow `METADATA`, if both are specified: - -[source,esql] ----- -FROM employees* METADATA _index OPTIONS "ignore_unavailable"="true" ----- diff --git a/docs/reference/ilm/apis/explain.asciidoc b/docs/reference/ilm/apis/explain.asciidoc index fbe017619048f..348a9e7f99e78 100644 --- a/docs/reference/ilm/apis/explain.asciidoc +++ b/docs/reference/ilm/apis/explain.asciidoc @@ -49,7 +49,7 @@ or `_all`. {ilm-init} and are in an error state, either due to an encountering an error while executing the policy, or attempting to use a policy that does not exist. -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] [[ilm-explain-lifecycle-example]] ==== {api-examples-title} diff --git a/docs/reference/ilm/apis/remove-policy-from-index.asciidoc b/docs/reference/ilm/apis/remove-policy-from-index.asciidoc index 20e0df9f3cb92..711eccc298df1 100644 --- a/docs/reference/ilm/apis/remove-policy-from-index.asciidoc +++ b/docs/reference/ilm/apis/remove-policy-from-index.asciidoc @@ -40,7 +40,7 @@ target. Supports wildcards (`*`). To target all data streams and indices, use [[ilm-remove-policy-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] [[ilm-remove-policy-example]] ==== {api-examples-title} diff --git a/docs/reference/indices/delete-component-template.asciidoc b/docs/reference/indices/delete-component-template.asciidoc index 0ca6560f17ccb..065a4adb90023 100644 --- a/docs/reference/indices/delete-component-template.asciidoc +++ b/docs/reference/indices/delete-component-template.asciidoc @@ -58,4 +58,4 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=component-template] [[delete-component-template-api-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] diff --git a/docs/reference/indices/delete-index-template-v1.asciidoc b/docs/reference/indices/delete-index-template-v1.asciidoc index ca0b5a0e726bd..98b1e2fb255f1 100644 --- a/docs/reference/indices/delete-index-template-v1.asciidoc +++ b/docs/reference/indices/delete-index-template-v1.asciidoc @@ -55,4 +55,4 @@ expressions are supported. [[delete-template-api-v1-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] diff --git a/docs/reference/indices/delete-index-template.asciidoc b/docs/reference/indices/delete-index-template.asciidoc index 02396310daff4..b828e4a536b71 100644 --- a/docs/reference/indices/delete-index-template.asciidoc +++ b/docs/reference/indices/delete-index-template.asciidoc @@ -61,4 +61,4 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=index-template] [[delete-template-api-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] diff --git a/docs/reference/indices/field-usage-stats.asciidoc b/docs/reference/indices/field-usage-stats.asciidoc index 9fd1d9e59eb33..a4856092834e5 100644 --- a/docs/reference/indices/field-usage-stats.asciidoc +++ b/docs/reference/indices/field-usage-stats.asciidoc @@ -46,8 +46,6 @@ include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=index-ignore-unavailabl include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=wait_for_active_shards] -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] - `fields`:: + -- diff --git a/docs/reference/inference/put-inference.asciidoc b/docs/reference/inference/put-inference.asciidoc index 1f73cd08401ee..c256b30060bf6 100644 --- a/docs/reference/inference/put-inference.asciidoc +++ b/docs/reference/inference/put-inference.asciidoc @@ -268,7 +268,7 @@ used for abuse detection. ===== `return_documents`:: (Optional, boolean) -For `cohere` service only. Specify whether to return doc text within the +For `cohere` service only. Specify whether to return doc text within the results. `top_n`:: @@ -307,16 +307,6 @@ For `openai` and `azureopenai` service only. Specifies the user issuing the request, which can be used for abuse detection. ===== -+ -.`task_settings` for the `completion` task type -[%collapsible%closed] -===== -`user`::: -(optional, string) -For `openai` service only. Specifies the user issuing the request, which can be used for abuse detection. -===== - - [discrete] [[put-inference-api-example]] ==== {api-examples-title} @@ -351,11 +341,11 @@ The following example shows how to create an {infer} endpoint called [source,console] ------------------------------------------------------------ -PUT _inference/rerank/cohere-rerank +PUT _inference/rerank/cohere-rerank { "service": "cohere", "service_settings": { - "api_key": "", + "api_key": "", "model_id": "rerank-english-v3.0" }, "task_settings": { @@ -366,7 +356,7 @@ PUT _inference/rerank/cohere-rerank ------------------------------------------------------------ // TEST[skip:TBD] -For more examples, also review the +For more examples, also review the https://docs.cohere.com/docs/elasticsearch-and-cohere#rerank-search-results-with-cohere-and-elasticsearch[Cohere documentation]. diff --git a/docs/reference/ingest/processors/geoip.asciidoc b/docs/reference/ingest/processors/geoip.asciidoc index 12e7a5f10135c..a8c6a8f647c74 100644 --- a/docs/reference/ingest/processors/geoip.asciidoc +++ b/docs/reference/ingest/processors/geoip.asciidoc @@ -59,10 +59,18 @@ in `properties`. * If the GeoIP2 Anonymous IP database is used, then the following fields may be added under the `target_field`: `ip`, `hosting_provider`, `tor_exit_node`, `anonymous_vpn`, `anonymous`, `public_proxy`, and `residential_proxy`. The fields actually added depend on what has been found and which properties were configured in `properties`. +* If the GeoIP2 Connection Type database is used, then the following fields may be added under the `target_field`: `ip`, and +`connection_type`. The fields actually added depend on what has been found and which properties were configured in `properties`. +* If the GeoIP2 Domain database is used, then the following fields may be added under the `target_field`: `ip`, and `domain`. +The fields actually added depend on what has been found and which properties were configured in `properties`. +* If the GeoIP2 ISP database is used, then the following fields may be added under the `target_field`: `ip`, `asn`, +`organization_name`, `network`, `isp`, `isp_organization`, `mobile_country_code`, and `mobile_network_code`. The fields actually added +depend on what has been found and which properties were configured in `properties`. * If the GeoIP2 Enterprise database is used, then the following fields may be added under the `target_field`: `ip`, `country_iso_code`, `country_name`, `continent_name`, `region_iso_code`, `region_name`, `city_name`, `timezone`, `location`, `asn`, -`organization_name`, `network`, `hosting_provider`, `tor_exit_node`, `anonymous_vpn`, `anonymous`, `public_proxy`, and `residential_proxy`. -The fields actually added depend on what has been found and which properties were configured in `properties`. +`organization_name`, `network`, `hosting_provider`, `tor_exit_node`, `anonymous_vpn`, `anonymous`, `public_proxy`, `residential_proxy`, +`domain`, `isp`, `isp_organization`, `mobile_country_code`, `mobile_network_code`, `user_type`, and `connection_type`. The fields +actually added depend on what has been found and which properties were configured in `properties`. Here is an example that uses the default city database and adds the geographical information to the `geoip` field based on the `ip` field: diff --git a/docs/reference/rest-api/common-parms.asciidoc b/docs/reference/rest-api/common-parms.asciidoc index a2a397c4efe65..15414dde86e52 100644 --- a/docs/reference/rest-api/common-parms.asciidoc +++ b/docs/reference/rest-api/common-parms.asciidoc @@ -1232,6 +1232,15 @@ indicate that it was not completely acknowledged. Defaults to `30s`. Can also be set to `-1` to indicate that the request should never timeout. end::timeoutparms[] +tag::timeout-nodes-request[] +`timeout`:: +(Optional, <>) +Period to wait for each node to respond. If a node does not respond before its +timeout expires, the response does not include its information. However, timed out +nodes are included in the response's `_nodes.failed` property. Defaults to no +timeout. +end::timeout-nodes-request[] + tag::transform-id[] Identifier for the {transform}. end::transform-id[] diff --git a/docs/reference/rest-api/watcher/start.asciidoc b/docs/reference/rest-api/watcher/start.asciidoc index 565ef60160a9d..b153410ed2901 100644 --- a/docs/reference/rest-api/watcher/start.asciidoc +++ b/docs/reference/rest-api/watcher/start.asciidoc @@ -24,10 +24,8 @@ information, see <>. //[[watcher-api-start-path-params]] //==== {api-path-parms-title} -[[watcher-api-start-query-params]] -==== {api-query-parms-title} - -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] +//[[watcher-api-start-query-params]] +//==== {api-query-parms-title} //[[watcher-api-start-request-body]] //==== {api-request-body-title} diff --git a/docs/reference/search/retriever.asciidoc b/docs/reference/search/retriever.asciidoc index c47ccd60afc05..590df272cc89e 100644 --- a/docs/reference/search/retriever.asciidoc +++ b/docs/reference/search/retriever.asciidoc @@ -12,6 +12,11 @@ allows for complex behavior to be depicted in a tree-like structure, called the retriever tree, to better clarify the order of operations that occur during a search. +[TIP] +==== +Refer to <> for a high level overview of the retrievers abstraction. +==== + The following retrievers are available: `standard`:: diff --git a/docs/reference/searchable-snapshots/apis/node-cache-stats.asciidoc b/docs/reference/searchable-snapshots/apis/node-cache-stats.asciidoc index 50314b6d36f28..62faceb99d4fc 100644 --- a/docs/reference/searchable-snapshots/apis/node-cache-stats.asciidoc +++ b/docs/reference/searchable-snapshots/apis/node-cache-stats.asciidoc @@ -30,10 +30,8 @@ For more information, see <>. For example, `nodeId1,nodeId2`. For node selection options, see <>. -[[searchable-snapshots-api-cache-stats-query-params]] -==== {api-query-parms-title} - -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] +//[[searchable-snapshots-api-cache-stats-query-params]] +//==== {api-query-parms-title} [role="child_attributes"] [[searchable-snapshots-api-cache-stats-response-body]] diff --git a/docs/reference/shutdown/apis/shutdown-delete.asciidoc b/docs/reference/shutdown/apis/shutdown-delete.asciidoc index 133539adfaa38..4d7f30c3a1e48 100644 --- a/docs/reference/shutdown/apis/shutdown-delete.asciidoc +++ b/docs/reference/shutdown/apis/shutdown-delete.asciidoc @@ -40,7 +40,7 @@ The ID of a node that you prepared for shut down. [[delete-shutdown-api-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] [[delete-shutdown-api-example]] ==== {api-examples-title} diff --git a/docs/reference/shutdown/apis/shutdown-get.asciidoc b/docs/reference/shutdown/apis/shutdown-get.asciidoc index 264a8dd7be181..5feac28353ab5 100644 --- a/docs/reference/shutdown/apis/shutdown-get.asciidoc +++ b/docs/reference/shutdown/apis/shutdown-get.asciidoc @@ -37,10 +37,8 @@ Use to monitor the shut down process after calling < The ID of a node that is being prepared for shutdown. If no ID is specified, returns the status of all nodes being prepared for shutdown. -[[get-shutdown-api-params]] -==== {api-query-parms-title} - -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +//[[get-shutdown-api-params]] +//==== {api-query-parms-title} [[get-shutdown-api-example]] ==== {api-examples-title} diff --git a/docs/reference/shutdown/apis/shutdown-put.asciidoc b/docs/reference/shutdown/apis/shutdown-put.asciidoc index 236367f886ef9..344dd8fa36717 100644 --- a/docs/reference/shutdown/apis/shutdown-put.asciidoc +++ b/docs/reference/shutdown/apis/shutdown-put.asciidoc @@ -50,7 +50,7 @@ No error is thrown if you specify an invalid node ID. [[put-shutdown-api-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] [role="child_attributes"] [[put-shutdown-api-request-body]] diff --git a/docs/reference/snapshot-restore/apis/delete-repo-api.asciidoc b/docs/reference/snapshot-restore/apis/delete-repo-api.asciidoc index 2931faf49841d..4301fea642523 100644 --- a/docs/reference/snapshot-restore/apis/delete-repo-api.asciidoc +++ b/docs/reference/snapshot-restore/apis/delete-repo-api.asciidoc @@ -51,9 +51,4 @@ supported. [[delete-snapshot-repo-api-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] - -`timeout`:: -(Optional, <>) Specifies the period of time to wait for -a response. If no response is received before the timeout expires, the request -fails and returns an error. Defaults to `30s`. +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] diff --git a/docs/reference/snapshot-restore/apis/put-repo-api.asciidoc b/docs/reference/snapshot-restore/apis/put-repo-api.asciidoc index c3e9c0a0904be..0d3b5586da869 100644 --- a/docs/reference/snapshot-restore/apis/put-repo-api.asciidoc +++ b/docs/reference/snapshot-restore/apis/put-repo-api.asciidoc @@ -52,12 +52,7 @@ IMPORTANT: Several options for this API can be specified using a query parameter or a request body parameter. If both parameters are specified, only the query parameter is used. -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] - -`timeout`:: -(Optional, <>) Specifies the period of time to wait for -a response. If no response is received before the timeout expires, the request -fails and returns an error. Defaults to `30s`. +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] `verify`:: (Optional, Boolean) diff --git a/docs/reference/snapshot-restore/apis/verify-repo-api.asciidoc b/docs/reference/snapshot-restore/apis/verify-repo-api.asciidoc index 9d14e8a426e32..dd845663be8d7 100644 --- a/docs/reference/snapshot-restore/apis/verify-repo-api.asciidoc +++ b/docs/reference/snapshot-restore/apis/verify-repo-api.asciidoc @@ -47,12 +47,7 @@ Name of the snapshot repository to verify. [[verify-snapshot-repo-api-query-params]] ==== {api-query-parms-title} -include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=master-timeout] - -`timeout`:: -(Optional, <>) Specifies the period of time to wait for -a response. If no response is received before the timeout expires, the request -fails and returns an error. Defaults to `30s`. +include::{es-ref-dir}/rest-api/common-parms.asciidoc[tag=timeoutparms] [role="child_attributes"] [[verify-snapshot-repo-api-response-body]] diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 000a2c15f5b12..53db6f13a31b3 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1589,9 +1589,9 @@ - - - + + + @@ -1720,6 +1720,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -3701,13 +3722,13 @@ - - - + + + - - + + diff --git a/modules/data-streams/build.gradle b/modules/data-streams/build.gradle index 8acdb0f156af1..b9d38551a2674 100644 --- a/modules/data-streams/build.gradle +++ b/modules/data-streams/build.gradle @@ -20,6 +20,7 @@ restResources { dependencies { testImplementation project(path: ':test:test-clusters') + internalClusterTestImplementation project(":modules:mapper-extras") } tasks.named('yamlRestTest') { diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java index 2b1a8e1c0e318..f79eea8676b3e 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java @@ -1281,7 +1281,7 @@ public void testSearchAllResolvesDataStreams() throws Exception { public void testGetDataStream() throws Exception { Settings settings = Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, maximumNumberOfReplicas() + 2).build(); DataStreamLifecycle lifecycle = DataStreamLifecycle.newBuilder().dataRetention(randomMillisUpToYear9999()).build(); - putComposableIndexTemplate("template_for_foo", null, List.of("metrics-foo*"), settings, null, null, lifecycle); + putComposableIndexTemplate("template_for_foo", null, List.of("metrics-foo*"), settings, null, null, lifecycle, false); int numDocsFoo = randomIntBetween(2, 16); indexDocs("metrics-foo", numDocsFoo); @@ -1642,7 +1642,8 @@ public void testCreateDataStreamWithSameNameAsDataStreamAlias() throws Exception null, null, Map.of("my-alias", AliasMetadata.builder("my-alias").build()), - null + null, + false ); var request = new CreateDataStreamAction.Request("my-ds"); assertAcked(client().execute(CreateDataStreamAction.INSTANCE, request).actionGet()); @@ -1675,7 +1676,8 @@ public void testCreateDataStreamAliasWithSameNameAsIndexAlias() throws Exception null, null, Map.of("logs", AliasMetadata.builder("logs").build()), - null + null, + false ); var request = new CreateDataStreamAction.Request("logs-es"); @@ -1712,7 +1714,8 @@ public void testCreateDataStreamAliasWithSameNameAsIndex() throws Exception { null, null, Map.of("logs", AliasMetadata.builder("logs").build()), - null + null, + false ) ); assertThat( @@ -1902,7 +1905,11 @@ static void verifyDocs(String dataStream, long expectedNumHits, long minGenerati } public static void putComposableIndexTemplate(String id, List patterns) throws IOException { - putComposableIndexTemplate(id, null, patterns, null, null); + putComposableIndexTemplate(id, patterns, false); + } + + public static void putComposableIndexTemplate(String id, List patterns, boolean withFailureStore) throws IOException { + putComposableIndexTemplate(id, null, patterns, null, null, null, null, withFailureStore); } public void testPartitionedTemplate() throws IOException { @@ -2277,7 +2284,7 @@ static void putComposableIndexTemplate( @Nullable Settings settings, @Nullable Map metadata ) throws IOException { - putComposableIndexTemplate(id, mappings, patterns, settings, metadata, null, null); + putComposableIndexTemplate(id, mappings, patterns, settings, metadata, null, null, false); } static void putComposableIndexTemplate( @@ -2287,7 +2294,8 @@ static void putComposableIndexTemplate( @Nullable Settings settings, @Nullable Map metadata, @Nullable Map aliases, - @Nullable DataStreamLifecycle lifecycle + @Nullable DataStreamLifecycle lifecycle, + boolean withFailureStore ) throws IOException { TransportPutComposableIndexTemplateAction.Request request = new TransportPutComposableIndexTemplateAction.Request(id); request.indexTemplate( @@ -2295,7 +2303,7 @@ static void putComposableIndexTemplate( .indexPatterns(patterns) .template(new Template(settings, mappings == null ? null : CompressedXContent.fromJSON(mappings), aliases, lifecycle)) .metadata(metadata) - .dataStreamTemplate(new ComposableIndexTemplate.DataStreamTemplate()) + .dataStreamTemplate(new ComposableIndexTemplate.DataStreamTemplate(false, false, withFailureStore)) .build() ); client().execute(TransportPutComposableIndexTemplateAction.TYPE, request).actionGet(); diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamsSnapshotsIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamsSnapshotsIT.java index da782cfd86ce2..1bd4d54b9c804 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamsSnapshotsIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamsSnapshotsIT.java @@ -36,6 +36,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.index.Index; +import org.elasticsearch.index.mapper.extras.MapperExtrasPlugin; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestStatus; @@ -81,13 +82,17 @@ public class DataStreamsSnapshotsIT extends AbstractSnapshotIntegTestCase { private String dsBackingIndexName; private String otherDsBackingIndexName; + private String fsBackingIndexName; + private String fsFailureIndexName; private String ds2BackingIndexName; private String otherDs2BackingIndexName; + private String fs2BackingIndexName; + private String fs2FailureIndexName; private String id; @Override protected Collection> nodePlugins() { - return List.of(MockRepository.Plugin.class, DataStreamsPlugin.class); + return List.of(MockRepository.Plugin.class, DataStreamsPlugin.class, MapperExtrasPlugin.class); } @Before @@ -97,6 +102,18 @@ public void setup() throws Exception { createRepository(REPO, "fs", location); DataStreamIT.putComposableIndexTemplate("t1", List.of("ds", "other-ds")); + DataStreamIT.putComposableIndexTemplate("t2", """ + { + "properties": { + "@timestamp": { + "type": "date", + "format": "date_optional_time||epoch_millis" + }, + "flag": { + "type": "boolean" + } + } + }""", List.of("with-fs"), null, null, null, null, true); CreateDataStreamAction.Request request = new CreateDataStreamAction.Request("ds"); AcknowledgedResponse response = client.execute(CreateDataStreamAction.INSTANCE, request).get(); @@ -106,15 +123,30 @@ public void setup() throws Exception { response = client.execute(CreateDataStreamAction.INSTANCE, request).get(); assertTrue(response.isAcknowledged()); + request = new CreateDataStreamAction.Request("with-fs"); + response = client.execute(CreateDataStreamAction.INSTANCE, request).get(); + assertTrue(response.isAcknowledged()); + // Resolve backing index names after data streams have been created: // (these names have a date component, and running around midnight could lead to test failures otherwise) GetDataStreamAction.Request getDataStreamRequest = new GetDataStreamAction.Request(new String[] { "*" }); GetDataStreamAction.Response getDataStreamResponse = client.execute(GetDataStreamAction.INSTANCE, getDataStreamRequest).actionGet(); dsBackingIndexName = getDataStreamResponse.getDataStreams().get(0).getDataStream().getIndices().get(0).getName(); otherDsBackingIndexName = getDataStreamResponse.getDataStreams().get(1).getDataStream().getIndices().get(0).getName(); + fsBackingIndexName = getDataStreamResponse.getDataStreams().get(2).getDataStream().getIndices().get(0).getName(); + fsFailureIndexName = getDataStreamResponse.getDataStreams() + .get(2) + .getDataStream() + .getFailureIndices() + .getIndices() + .get(0) + .getName(); + // Will be used in some tests, to test renaming while restoring a snapshot: ds2BackingIndexName = dsBackingIndexName.replace("-ds-", "-ds2-"); otherDs2BackingIndexName = otherDsBackingIndexName.replace("-other-ds-", "-other-ds2-"); + fs2BackingIndexName = fsBackingIndexName.replace("-with-fs-", "-with-fs2-"); + fs2FailureIndexName = fsFailureIndexName.replace("-with-fs-", "-with-fs2-"); DocWriteResponse indexResponse = client.prepareIndex("ds") .setOpType(DocWriteRequest.OpType.CREATE) @@ -232,12 +264,16 @@ public void testSnapshotAndRestoreAllDataStreamsInPlace() throws Exception { GetDataStreamAction.Response ds = client.execute(GetDataStreamAction.INSTANCE, getDataSteamRequest).get(); assertThat( ds.getDataStreams().stream().map(e -> e.getDataStream().getName()).collect(Collectors.toList()), - contains(equalTo("ds"), equalTo("other-ds")) + contains(equalTo("ds"), equalTo("other-ds"), equalTo("with-fs")) ); List backingIndices = ds.getDataStreams().get(0).getDataStream().getIndices(); assertThat(backingIndices.stream().map(Index::getName).collect(Collectors.toList()), contains(dsBackingIndexName)); backingIndices = ds.getDataStreams().get(1).getDataStream().getIndices(); assertThat(backingIndices.stream().map(Index::getName).collect(Collectors.toList()), contains(otherDsBackingIndexName)); + backingIndices = ds.getDataStreams().get(2).getDataStream().getIndices(); + assertThat(backingIndices.stream().map(Index::getName).collect(Collectors.toList()), contains(fsBackingIndexName)); + List failureIndices = ds.getDataStreams().get(2).getDataStream().getFailureIndices().getIndices(); + assertThat(failureIndices.stream().map(Index::getName).collect(Collectors.toList()), contains(fsFailureIndexName)); } public void testSnapshotAndRestoreInPlace() { @@ -295,13 +331,72 @@ public void testSnapshotAndRestoreInPlace() { // The backing index created as part of rollover should still exist (but just not part of the data stream) assertThat(indexExists(backingIndexAfterSnapshot), is(true)); - // An additional rollover should create a new backing index (3th generation) and leave .ds-ds-...-2 index as is: + // An additional rollover should create a new backing index (3rd generation) and leave .ds-ds-...-2 index as is: rolloverRequest = new RolloverRequest("ds", null); rolloverResponse = client.admin().indices().rolloverIndex(rolloverRequest).actionGet(); assertThat(rolloverResponse.isRolledOver(), is(true)); assertThat(rolloverResponse.getNewIndex(), equalTo(DataStream.getDefaultBackingIndexName("ds", 3))); } + public void testFailureStoreSnapshotAndRestore() throws Exception { + CreateSnapshotResponse createSnapshotResponse = client.admin() + .cluster() + .prepareCreateSnapshot(REPO, SNAPSHOT) + .setWaitForCompletion(true) + .setIndices("with-fs") + .setIncludeGlobalState(false) + .get(); + + RestStatus status = createSnapshotResponse.getSnapshotInfo().status(); + assertEquals(RestStatus.OK, status); + + assertThat(getSnapshot(REPO, SNAPSHOT).indices(), containsInAnyOrder(fsBackingIndexName, fsFailureIndexName)); + + assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request("with-fs"))); + + { + RestoreSnapshotResponse restoreSnapshotResponse = client.admin() + .cluster() + .prepareRestoreSnapshot(REPO, SNAPSHOT) + .setWaitForCompletion(true) + .setIndices("with-fs") + .get(); + + assertEquals(2, restoreSnapshotResponse.getRestoreInfo().successfulShards()); + + GetDataStreamAction.Response ds = client.execute( + GetDataStreamAction.INSTANCE, + new GetDataStreamAction.Request(new String[] { "with-fs" }) + ).get(); + assertEquals(1, ds.getDataStreams().size()); + assertEquals(1, ds.getDataStreams().get(0).getDataStream().getIndices().size()); + assertEquals(fsBackingIndexName, ds.getDataStreams().get(0).getDataStream().getIndices().get(0).getName()); + assertEquals(fsFailureIndexName, ds.getDataStreams().get(0).getDataStream().getFailureIndices().getIndices().get(0).getName()); + } + { + // With rename pattern + RestoreSnapshotResponse restoreSnapshotResponse = client.admin() + .cluster() + .prepareRestoreSnapshot(REPO, SNAPSHOT) + .setWaitForCompletion(true) + .setIndices("with-fs") + .setRenamePattern("-fs") + .setRenameReplacement("-fs2") + .get(); + + assertEquals(2, restoreSnapshotResponse.getRestoreInfo().successfulShards()); + + GetDataStreamAction.Response ds = client.execute( + GetDataStreamAction.INSTANCE, + new GetDataStreamAction.Request(new String[] { "with-fs2" }) + ).get(); + assertEquals(1, ds.getDataStreams().size()); + assertEquals(1, ds.getDataStreams().get(0).getDataStream().getIndices().size()); + assertEquals(fs2BackingIndexName, ds.getDataStreams().get(0).getDataStream().getIndices().get(0).getName()); + assertEquals(fs2FailureIndexName, ds.getDataStreams().get(0).getDataStream().getFailureIndices().getIndices().get(0).getName()); + } + } + public void testSnapshotAndRestoreAllIncludeSpecificDataStream() throws Exception { DocWriteResponse indexResponse = client.prepareIndex("other-ds") .setOpType(DocWriteRequest.OpType.CREATE) @@ -338,10 +433,13 @@ public void testSnapshotAndRestoreAllIncludeSpecificDataStream() throws Exceptio if (filterDuringSnapshotting) { assertThat(getSnapshot(REPO, SNAPSHOT).indices(), containsInAnyOrder(backingIndexName)); } else { - assertThat(getSnapshot(REPO, SNAPSHOT).indices(), containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName)); + assertThat( + getSnapshot(REPO, SNAPSHOT).indices(), + containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName, fsBackingIndexName, fsFailureIndexName) + ); } - assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request(new String[] { "*" })).get()); + assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request("*")).get()); assertAcked(client.admin().indices().prepareDelete("*").setIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED_HIDDEN)); RestoreSnapshotRequest restoreSnapshotRequest = new RestoreSnapshotRequest(REPO, SNAPSHOT); @@ -395,7 +493,10 @@ public void testSnapshotAndRestoreReplaceAll() throws Exception { RestStatus status = createSnapshotResponse.getSnapshotInfo().status(); assertEquals(RestStatus.OK, status); - assertThat(getSnapshot(REPO, SNAPSHOT).indices(), containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName)); + assertThat( + getSnapshot(REPO, SNAPSHOT).indices(), + containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName, fsBackingIndexName, fsFailureIndexName) + ); assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request(new String[] { "*" })).get()); assertAcked(client.admin().indices().prepareDelete("*").setIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED_HIDDEN)); @@ -403,7 +504,7 @@ public void testSnapshotAndRestoreReplaceAll() throws Exception { var restoreSnapshotRequest = new RestoreSnapshotRequest(REPO, SNAPSHOT).waitForCompletion(true).includeGlobalState(false); RestoreSnapshotResponse restoreSnapshotResponse = client.admin().cluster().restoreSnapshot(restoreSnapshotRequest).actionGet(); - assertEquals(2, restoreSnapshotResponse.getRestoreInfo().successfulShards()); + assertEquals(4, restoreSnapshotResponse.getRestoreInfo().successfulShards()); assertEquals(DOCUMENT_SOURCE, client.prepareGet(dsBackingIndexName, id).get().getSourceAsMap()); assertResponse(client.prepareSearch("ds"), response -> { @@ -416,10 +517,10 @@ public void testSnapshotAndRestoreReplaceAll() throws Exception { GetDataStreamAction.INSTANCE, new GetDataStreamAction.Request(new String[] { "*" }) ).get(); - assertEquals(2, ds.getDataStreams().size()); + assertEquals(3, ds.getDataStreams().size()); assertThat( ds.getDataStreams().stream().map(i -> i.getDataStream().getName()).collect(Collectors.toList()), - containsInAnyOrder("ds", "other-ds") + containsInAnyOrder("ds", "other-ds", "with-fs") ); GetAliasesResponse getAliasesResponse = client.admin().indices().getAliases(new GetAliasesRequest("my-alias")).actionGet(); @@ -451,14 +552,17 @@ public void testSnapshotAndRestoreAll() throws Exception { RestStatus status = createSnapshotResponse.getSnapshotInfo().status(); assertEquals(RestStatus.OK, status); - assertThat(getSnapshot(REPO, SNAPSHOT).indices(), containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName)); + assertThat( + getSnapshot(REPO, SNAPSHOT).indices(), + containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName, fsBackingIndexName, fsFailureIndexName) + ); - assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request(new String[] { "*" })).get()); + assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request("*")).get()); assertAcked(client.admin().indices().prepareDelete("*").setIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED_HIDDEN)); var restoreSnapshotRequest = new RestoreSnapshotRequest(REPO, SNAPSHOT).waitForCompletion(true).includeGlobalState(false); RestoreSnapshotResponse restoreSnapshotResponse = client.admin().cluster().restoreSnapshot(restoreSnapshotRequest).actionGet(); - assertEquals(2, restoreSnapshotResponse.getRestoreInfo().successfulShards()); + assertEquals(4, restoreSnapshotResponse.getRestoreInfo().successfulShards()); assertEquals(DOCUMENT_SOURCE, client.prepareGet(dsBackingIndexName, id).get().getSourceAsMap()); assertResponse(client.prepareSearch("ds"), response -> { @@ -471,11 +575,15 @@ public void testSnapshotAndRestoreAll() throws Exception { GetDataStreamAction.INSTANCE, new GetDataStreamAction.Request(new String[] { "*" }) ).get(); - assertEquals(2, ds.getDataStreams().size()); + assertEquals(3, ds.getDataStreams().size()); assertEquals(1, ds.getDataStreams().get(0).getDataStream().getIndices().size()); assertEquals(dsBackingIndexName, ds.getDataStreams().get(0).getDataStream().getIndices().get(0).getName()); assertEquals(1, ds.getDataStreams().get(1).getDataStream().getIndices().size()); assertEquals(otherDsBackingIndexName, ds.getDataStreams().get(1).getDataStream().getIndices().get(0).getName()); + assertEquals(1, ds.getDataStreams().get(2).getDataStream().getIndices().size()); + assertEquals(fsBackingIndexName, ds.getDataStreams().get(2).getDataStream().getIndices().get(0).getName()); + assertEquals(1, ds.getDataStreams().get(2).getDataStream().getFailureIndices().getIndices().size()); + assertEquals(fsFailureIndexName, ds.getDataStreams().get(2).getDataStream().getFailureIndices().getIndices().get(0).getName()); GetAliasesResponse getAliasesResponse = client.admin().indices().getAliases(new GetAliasesRequest("my-alias")).actionGet(); assertThat(getAliasesResponse.getDataStreamAliases().keySet(), containsInAnyOrder("ds", "other-ds")); @@ -507,16 +615,19 @@ public void testSnapshotAndRestoreIncludeAliasesFalse() throws Exception { RestStatus status = createSnapshotResponse.getSnapshotInfo().status(); assertEquals(RestStatus.OK, status); - assertThat(getSnapshot(REPO, SNAPSHOT).indices(), containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName)); + assertThat( + getSnapshot(REPO, SNAPSHOT).indices(), + containsInAnyOrder(dsBackingIndexName, otherDsBackingIndexName, fsBackingIndexName, fsFailureIndexName) + ); - assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request(new String[] { "*" })).get()); + assertAcked(client.execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request("*")).get()); assertAcked(client.admin().indices().prepareDelete("*").setIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN_CLOSED_HIDDEN)); var restoreSnapshotRequest = new RestoreSnapshotRequest(REPO, SNAPSHOT).waitForCompletion(true) .includeGlobalState(false) .includeAliases(false); RestoreSnapshotResponse restoreSnapshotResponse = client.admin().cluster().restoreSnapshot(restoreSnapshotRequest).actionGet(); - assertEquals(2, restoreSnapshotResponse.getRestoreInfo().successfulShards()); + assertEquals(4, restoreSnapshotResponse.getRestoreInfo().successfulShards()); assertEquals(DOCUMENT_SOURCE, client.prepareGet(dsBackingIndexName, id).get().getSourceAsMap()); assertResponse(client.prepareSearch("ds"), response -> { @@ -529,11 +640,15 @@ public void testSnapshotAndRestoreIncludeAliasesFalse() throws Exception { GetDataStreamAction.INSTANCE, new GetDataStreamAction.Request(new String[] { "*" }) ).get(); - assertEquals(2, ds.getDataStreams().size()); + assertEquals(3, ds.getDataStreams().size()); assertEquals(1, ds.getDataStreams().get(0).getDataStream().getIndices().size()); assertEquals(dsBackingIndexName, ds.getDataStreams().get(0).getDataStream().getIndices().get(0).getName()); assertEquals(1, ds.getDataStreams().get(1).getDataStream().getIndices().size()); assertEquals(otherDsBackingIndexName, ds.getDataStreams().get(1).getDataStream().getIndices().get(0).getName()); + assertEquals(1, ds.getDataStreams().get(2).getDataStream().getIndices().size()); + assertEquals(fsBackingIndexName, ds.getDataStreams().get(2).getDataStream().getIndices().get(0).getName()); + assertEquals(1, ds.getDataStreams().get(2).getDataStream().getIndices().size()); + assertEquals(fsFailureIndexName, ds.getDataStreams().get(2).getDataStream().getFailureIndices().getIndices().get(0).getName()); GetAliasesResponse getAliasesResponse = client.admin().indices().getAliases(new GetAliasesRequest("*")).actionGet(); assertThat(getAliasesResponse.getDataStreamAliases(), anEmptyMap()); @@ -930,7 +1045,32 @@ public void testPartialRestoreSnapshotThatIncludesDataStream() { .prepareRestoreSnapshot(REPO, snapshot) .setIndices(indexWithoutDataStream) .setWaitForCompletion(true) - .setRestoreGlobalState(randomBoolean()) + .setRestoreGlobalState(false) + .get() + .getRestoreInfo(); + assertThat(restoreInfo.failedShards(), is(0)); + assertThat(restoreInfo.successfulShards(), is(1)); + } + + /** + * This test is a copy of the {@link #testPartialRestoreSnapshotThatIncludesDataStream()} the only difference + * is that one include the global state and one doesn't. In general this shouldn't matter that's why it used to be + * a random parameter of the test, but because of #107515 it fails when we include the global state. Keep them + * separate until this is fixed. + */ + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/107515") + public void testPartialRestoreSnapshotThatIncludesDataStreamWithGlobalState() { + final String snapshot = "test-snapshot"; + final String indexWithoutDataStream = "test-idx-no-ds"; + createIndexWithContent(indexWithoutDataStream); + createFullSnapshot(REPO, snapshot); + assertAcked(client.admin().indices().prepareDelete(indexWithoutDataStream)); + RestoreInfo restoreInfo = client.admin() + .cluster() + .prepareRestoreSnapshot(REPO, snapshot) + .setIndices(indexWithoutDataStream) + .setWaitForCompletion(true) + .setRestoreGlobalState(true) .get() .getRestoreInfo(); assertThat(restoreInfo.failedShards(), is(0)); @@ -1027,7 +1167,32 @@ public void testExcludeDSFromSnapshotWhenExcludingItsIndices() { .cluster() .prepareRestoreSnapshot(REPO, snapshot) .setWaitForCompletion(true) - .setRestoreGlobalState(randomBoolean()) + .setRestoreGlobalState(false) + .get() + .getRestoreInfo(); + assertThat(restoreInfo.failedShards(), is(0)); + assertThat(restoreInfo.successfulShards(), is(1)); + } + + /** + * This test is a copy of the {@link #testExcludeDSFromSnapshotWhenExcludingItsIndices()} the only difference + * is that one include the global state and one doesn't. In general this shouldn't matter that's why it used to be + * a random parameter of the test, but because of #107515 it fails when we include the global state. Keep them + * separate until this is fixed. + */ + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/107515") + public void testExcludeDSFromSnapshotWhenExcludingItsIndicesWithGlobalState() { + final String snapshot = "test-snapshot"; + final String indexWithoutDataStream = "test-idx-no-ds"; + createIndexWithContent(indexWithoutDataStream); + final SnapshotInfo snapshotInfo = createSnapshot(REPO, snapshot, List.of("*", "-.*")); + assertThat(snapshotInfo.dataStreams(), empty()); + assertAcked(client.admin().indices().prepareDelete(indexWithoutDataStream)); + RestoreInfo restoreInfo = client.admin() + .cluster() + .prepareRestoreSnapshot(REPO, snapshot) + .setWaitForCompletion(true) + .setRestoreGlobalState(true) .get() .getRestoreInfo(); assertThat(restoreInfo.failedShards(), is(0)); @@ -1051,7 +1216,7 @@ public void testRestoreSnapshotFully() throws Exception { assertEquals(RestStatus.OK, restoreSnapshotResponse.status()); GetDataStreamAction.Request getRequest = new GetDataStreamAction.Request(new String[] { "*" }); - assertThat(client.execute(GetDataStreamAction.INSTANCE, getRequest).get().getDataStreams(), hasSize(2)); + assertThat(client.execute(GetDataStreamAction.INSTANCE, getRequest).get().getDataStreams(), hasSize(3)); assertNotNull(client.admin().indices().prepareGetIndex().setIndices(indexName).get()); } diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/CrudDataStreamLifecycleIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/CrudDataStreamLifecycleIT.java index d43dad87a6067..7712be94b4326 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/CrudDataStreamLifecycleIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/CrudDataStreamLifecycleIT.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.datastreams.CreateDataStreamAction; import org.elasticsearch.action.datastreams.lifecycle.GetDataStreamLifecycleAction; import org.elasticsearch.action.datastreams.lifecycle.PutDataStreamLifecycleAction; +import org.elasticsearch.action.support.master.AcknowledgedRequest; import org.elasticsearch.cluster.metadata.DataStreamLifecycle; import org.elasticsearch.core.TimeValue; import org.elasticsearch.datastreams.DataStreamsPlugin; @@ -229,6 +230,8 @@ public void testDeleteLifecycle() throws Exception { // Remove lifecycle from concrete data stream { DeleteDataStreamLifecycleAction.Request deleteDataLifecycleRequest = new DeleteDataStreamLifecycleAction.Request( + TimeValue.THIRTY_SECONDS, + AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, new String[] { "with-lifecycle-1" } ); assertThat( @@ -254,6 +257,8 @@ public void testDeleteLifecycle() throws Exception { // Remove lifecycle from all data streams { DeleteDataStreamLifecycleAction.Request deleteDataLifecycleRequest = new DeleteDataStreamLifecycleAction.Request( + TimeValue.THIRTY_SECONDS, + AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, new String[] { "*" } ); assertThat( diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/DataStreamLifecycleServiceIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/DataStreamLifecycleServiceIT.java index 7252d31d838c5..97c6c1ddff977 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/DataStreamLifecycleServiceIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/DataStreamLifecycleServiceIT.java @@ -203,6 +203,7 @@ public void testSystemDataStreamRetention() throws Exception { client().execute( PutDataStreamGlobalRetentionAction.INSTANCE, new PutDataStreamGlobalRetentionAction.Request( + TimeValue.THIRTY_SECONDS, TimeValue.timeValueSeconds(globalRetentionSeconds), TimeValue.timeValueSeconds(globalRetentionSeconds) ) @@ -290,7 +291,10 @@ public void testSystemDataStreamRetention() throws Exception { client().execute(DeleteDataStreamAction.INSTANCE, new DeleteDataStreamAction.Request(SYSTEM_DATA_STREAM_NAME)).actionGet(); } finally { - client().execute(DeleteDataStreamGlobalRetentionAction.INSTANCE, new DeleteDataStreamGlobalRetentionAction.Request()); + client().execute( + DeleteDataStreamGlobalRetentionAction.INSTANCE, + new DeleteDataStreamGlobalRetentionAction.Request(TimeValue.THIRTY_SECONDS) + ); } } finally { dataStreamLifecycleServices.forEach(dataStreamLifecycleService -> dataStreamLifecycleService.setNowSupplier(clock::millis)); diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/ExplainDataStreamLifecycleIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/ExplainDataStreamLifecycleIT.java index 2723637b2959b..35ee41fca18e8 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/ExplainDataStreamLifecycleIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/lifecycle/ExplainDataStreamLifecycleIT.java @@ -213,6 +213,7 @@ public void testSystemExplainLifecycle() throws Exception { client().execute( PutDataStreamGlobalRetentionAction.INSTANCE, new PutDataStreamGlobalRetentionAction.Request( + TimeValue.THIRTY_SECONDS, TimeValue.timeValueSeconds(globalRetentionSeconds), TimeValue.timeValueSeconds(globalRetentionSeconds) ) @@ -260,7 +261,10 @@ public void testSystemExplainLifecycle() throws Exception { ); } } finally { - client().execute(DeleteDataStreamGlobalRetentionAction.INSTANCE, new DeleteDataStreamGlobalRetentionAction.Request()); + client().execute( + DeleteDataStreamGlobalRetentionAction.INSTANCE, + new DeleteDataStreamGlobalRetentionAction.Request(TimeValue.THIRTY_SECONDS) + ); } } diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamGlobalRetentionAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamGlobalRetentionAction.java index e3cdd6a8c14d9..92cb855b7cb4e 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamGlobalRetentionAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamGlobalRetentionAction.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.datastreams.lifecycle.UpdateDataStreamGlobalRetentionService; import org.elasticsearch.features.FeatureService; import org.elasticsearch.tasks.Task; @@ -64,8 +65,8 @@ public void writeTo(StreamOutput out) throws IOException { out.writeBoolean(dryRun); } - public Request() { - super(TRAPPY_IMPLICIT_DEFAULT_MASTER_NODE_TIMEOUT); + public Request(TimeValue masterNodeTimeout) { + super(masterNodeTimeout); } public boolean dryRun() { diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamLifecycleAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamLifecycleAction.java index 3bd100a106dd6..70f822ddee72a 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamLifecycleAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/DeleteDataStreamLifecycleAction.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.TimeValue; import java.io.IOException; import java.util.Arrays; @@ -47,8 +48,8 @@ public void writeTo(StreamOutput out) throws IOException { indicesOptions.writeIndicesOptions(out); } - public Request(String[] names) { - super(TRAPPY_IMPLICIT_DEFAULT_MASTER_NODE_TIMEOUT, DEFAULT_ACK_TIMEOUT); + public Request(TimeValue masterNodeTimeout, TimeValue ackTimeout, String[] names) { + super(masterNodeTimeout, ackTimeout); this.names = names; } diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamGlobalRetentionAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamGlobalRetentionAction.java index 5816823ed710a..1d1064dd42b1a 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamGlobalRetentionAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamGlobalRetentionAction.java @@ -47,10 +47,6 @@ private GetDataStreamGlobalRetentionAction() {/* no instances */} public static final class Request extends MasterNodeReadRequest { - public Request() { - super(TRAPPY_IMPLICIT_DEFAULT_MASTER_NODE_TIMEOUT); - } - public Request(StreamInput in) throws IOException { super(in); } diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamLifecycleStatsAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamLifecycleStatsAction.java index cc61c7fe664be..6e930defd4e0b 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamLifecycleStatsAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/GetDataStreamLifecycleStatsAction.java @@ -43,8 +43,8 @@ public Request(StreamInput in) throws IOException { super(in); } - public Request() { - super(TRAPPY_IMPLICIT_DEFAULT_MASTER_NODE_TIMEOUT); + public Request(TimeValue masterNodeTimeout) { + super(masterNodeTimeout); } @Override diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/PutDataStreamGlobalRetentionAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/PutDataStreamGlobalRetentionAction.java index 65ca34a99da23..cd9156ad8b2c8 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/PutDataStreamGlobalRetentionAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/action/PutDataStreamGlobalRetentionAction.java @@ -32,9 +32,6 @@ import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xcontent.ConstructingObjectParser; -import org.elasticsearch.xcontent.ObjectParser; -import org.elasticsearch.xcontent.XContentParser; import java.io.IOException; import java.util.List; @@ -53,34 +50,9 @@ private PutDataStreamGlobalRetentionAction() {/* no instances */} public static final class Request extends MasterNodeRequest { - public static final ConstructingObjectParser PARSER = - new ConstructingObjectParser<>( - "put_data_stream_global_retention_request", - args -> new PutDataStreamGlobalRetentionAction.Request((TimeValue) args[0], (TimeValue) args[1]) - ); - - static { - PARSER.declareField( - ConstructingObjectParser.optionalConstructorArg(), - (p, c) -> TimeValue.parseTimeValue(p.textOrNull(), DataStreamGlobalRetention.DEFAULT_RETENTION_FIELD.getPreferredName()), - DataStreamGlobalRetention.DEFAULT_RETENTION_FIELD, - ObjectParser.ValueType.STRING_OR_NULL - ); - PARSER.declareField( - ConstructingObjectParser.optionalConstructorArg(), - (p, c) -> TimeValue.parseTimeValue(p.textOrNull(), DataStreamGlobalRetention.MAX_RETENTION_FIELD.getPreferredName()), - DataStreamGlobalRetention.MAX_RETENTION_FIELD, - ObjectParser.ValueType.STRING_OR_NULL - ); - } - private final DataStreamGlobalRetention globalRetention; private boolean dryRun = false; - public static PutDataStreamGlobalRetentionAction.Request parseRequest(XContentParser parser) { - return PARSER.apply(parser, null); - } - public Request(StreamInput in) throws IOException { super(in); globalRetention = DataStreamGlobalRetention.read(in); @@ -107,8 +79,8 @@ public void writeTo(StreamOutput out) throws IOException { out.writeBoolean(dryRun); } - public Request(@Nullable TimeValue defaultRetention, @Nullable TimeValue maxRetention) { - super(TRAPPY_IMPLICIT_DEFAULT_MASTER_NODE_TIMEOUT); + public Request(TimeValue masterNodeTimeout, @Nullable TimeValue defaultRetention, @Nullable TimeValue maxRetention) { + super(masterNodeTimeout); this.globalRetention = new DataStreamGlobalRetention(defaultRetention, maxRetention); } diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDataStreamLifecycleStatsAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDataStreamLifecycleStatsAction.java index a10a955b33975..a3959ae818218 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDataStreamLifecycleStatsAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDataStreamLifecycleStatsAction.java @@ -36,8 +36,7 @@ public List routes() { @Override protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) { - GetDataStreamLifecycleStatsAction.Request request = new GetDataStreamLifecycleStatsAction.Request(); - request.masterNodeTimeout(getMasterNodeTimeout(restRequest)); + final var request = new GetDataStreamLifecycleStatsAction.Request(getMasterNodeTimeout(restRequest)); return channel -> client.execute( GetDataStreamLifecycleStatsAction.INSTANCE, request, diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDeleteDataStreamLifecycleAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDeleteDataStreamLifecycleAction.java index b624892ac6bba..a8a64eaf5cfa3 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDeleteDataStreamLifecycleAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/lifecycle/rest/RestDeleteDataStreamLifecycleAction.java @@ -8,6 +8,7 @@ package org.elasticsearch.datastreams.lifecycle.rest; import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.action.support.master.AcknowledgedRequest; import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.common.Strings; import org.elasticsearch.datastreams.lifecycle.action.DeleteDataStreamLifecycleAction; @@ -20,6 +21,7 @@ import java.util.List; import static org.elasticsearch.rest.RestRequest.Method.DELETE; +import static org.elasticsearch.rest.RestUtils.getMasterNodeTimeout; @ServerlessScope(Scope.INTERNAL) public class RestDeleteDataStreamLifecycleAction extends BaseRestHandler { @@ -36,7 +38,9 @@ public List routes() { @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { - DeleteDataStreamLifecycleAction.Request deleteDataLifecycleRequest = new DeleteDataStreamLifecycleAction.Request( + final var deleteDataLifecycleRequest = new DeleteDataStreamLifecycleAction.Request( + getMasterNodeTimeout(request), + request.paramAsTime("timeout", AcknowledgedRequest.DEFAULT_ACK_TIMEOUT), Strings.splitStringByCommaToArray(request.param("name")) ); deleteDataLifecycleRequest.indicesOptions(IndicesOptions.fromRequest(request, deleteDataLifecycleRequest.indicesOptions())); diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java index 889b4c490d23f..fe7f03529a421 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/Database.java @@ -75,6 +75,8 @@ enum Database { Property.RESIDENTIAL_PROXY ) ), + ConnectionType(Set.of(Property.IP, Property.CONNECTION_TYPE), Set.of(Property.CONNECTION_TYPE)), + Domain(Set.of(Property.IP, Property.DOMAIN), Set.of(Property.DOMAIN)), Enterprise( Set.of( Property.IP, @@ -94,7 +96,14 @@ enum Database { Property.ANONYMOUS_VPN, Property.ANONYMOUS, Property.PUBLIC_PROXY, - Property.RESIDENTIAL_PROXY + Property.RESIDENTIAL_PROXY, + Property.DOMAIN, + Property.ISP, + Property.ISP_ORGANIZATION_NAME, + Property.MOBILE_COUNTRY_CODE, + Property.MOBILE_NETWORK_CODE, + Property.USER_TYPE, + Property.CONNECTION_TYPE ), Set.of( Property.COUNTRY_ISO_CODE, @@ -105,13 +114,38 @@ enum Database { Property.CITY_NAME, Property.LOCATION ) + ), + Isp( + Set.of( + Property.IP, + Property.ASN, + Property.ORGANIZATION_NAME, + Property.NETWORK, + Property.ISP, + Property.ISP_ORGANIZATION_NAME, + Property.MOBILE_COUNTRY_CODE, + Property.MOBILE_NETWORK_CODE + ), + Set.of( + Property.IP, + Property.ASN, + Property.ORGANIZATION_NAME, + Property.NETWORK, + Property.ISP, + Property.ISP_ORGANIZATION_NAME, + Property.MOBILE_COUNTRY_CODE, + Property.MOBILE_NETWORK_CODE + ) ); private static final String CITY_DB_SUFFIX = "-City"; private static final String COUNTRY_DB_SUFFIX = "-Country"; private static final String ASN_DB_SUFFIX = "-ASN"; private static final String ANONYMOUS_IP_DB_SUFFIX = "-Anonymous-IP"; + private static final String CONNECTION_TYPE_DB_SUFFIX = "-Connection-Type"; + private static final String DOMAIN_DB_SUFFIX = "-Domain"; private static final String ENTERPRISE_DB_SUFFIX = "-Enterprise"; + private static final String ISP_DB_SUFFIX = "-ISP"; /** * Parses the passed-in databaseType (presumably from the passed-in databaseFile) and return the Database instance that is @@ -133,8 +167,14 @@ public static Database getDatabase(final String databaseType, final String datab database = Database.Asn; } else if (databaseType.endsWith(Database.ANONYMOUS_IP_DB_SUFFIX)) { database = Database.AnonymousIp; + } else if (databaseType.endsWith(Database.CONNECTION_TYPE_DB_SUFFIX)) { + database = Database.ConnectionType; + } else if (databaseType.endsWith(Database.DOMAIN_DB_SUFFIX)) { + database = Database.Domain; } else if (databaseType.endsWith(Database.ENTERPRISE_DB_SUFFIX)) { database = Database.Enterprise; + } else if (databaseType.endsWith(Database.ISP_DB_SUFFIX)) { + database = Database.Isp; } } @@ -209,7 +249,14 @@ enum Property { ANONYMOUS_VPN, ANONYMOUS, PUBLIC_PROXY, - RESIDENTIAL_PROXY; + RESIDENTIAL_PROXY, + DOMAIN, + ISP, + ISP_ORGANIZATION_NAME, + MOBILE_COUNTRY_CODE, + MOBILE_NETWORK_CODE, + CONNECTION_TYPE, + USER_TYPE; /** * Parses a string representation of a property into an actual Property instance. Not all properties that exist are diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java index 12f6a299e1232..72873efd0d73f 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/DatabaseReaderLazyLoader.java @@ -15,8 +15,11 @@ import com.maxmind.geoip2.model.AnonymousIpResponse; import com.maxmind.geoip2.model.AsnResponse; import com.maxmind.geoip2.model.CityResponse; +import com.maxmind.geoip2.model.ConnectionTypeResponse; import com.maxmind.geoip2.model.CountryResponse; +import com.maxmind.geoip2.model.DomainResponse; import com.maxmind.geoip2.model.EnterpriseResponse; +import com.maxmind.geoip2.model.IspResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -177,12 +180,30 @@ public AnonymousIpResponse getAnonymousIp(InetAddress ipAddress) { return getResponse(ipAddress, DatabaseReader::tryAnonymousIp); } + @Nullable + @Override + public ConnectionTypeResponse getConnectionType(InetAddress ipAddress) { + return getResponse(ipAddress, DatabaseReader::tryConnectionType); + } + + @Nullable + @Override + public DomainResponse getDomain(InetAddress ipAddress) { + return getResponse(ipAddress, DatabaseReader::tryDomain); + } + @Nullable @Override public EnterpriseResponse getEnterprise(InetAddress ipAddress) { return getResponse(ipAddress, DatabaseReader::tryEnterprise); } + @Nullable + @Override + public IspResponse getIsp(InetAddress ipAddress) { + return getResponse(ipAddress, DatabaseReader::tryIsp); + } + boolean preLookup() { return currentUsages.updateAndGet(current -> current < 0 ? current : current + 1) > 0; } diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDatabase.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDatabase.java index 088fa2b0d1fa8..674c500f069bc 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDatabase.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpDatabase.java @@ -11,8 +11,11 @@ import com.maxmind.geoip2.model.AnonymousIpResponse; import com.maxmind.geoip2.model.AsnResponse; import com.maxmind.geoip2.model.CityResponse; +import com.maxmind.geoip2.model.ConnectionTypeResponse; import com.maxmind.geoip2.model.CountryResponse; +import com.maxmind.geoip2.model.DomainResponse; import com.maxmind.geoip2.model.EnterpriseResponse; +import com.maxmind.geoip2.model.IspResponse; import org.elasticsearch.core.Nullable; @@ -58,9 +61,18 @@ public interface GeoIpDatabase { @Nullable AnonymousIpResponse getAnonymousIp(InetAddress ipAddress); + @Nullable + ConnectionTypeResponse getConnectionType(InetAddress ipAddress); + + @Nullable + DomainResponse getDomain(InetAddress ipAddress); + @Nullable EnterpriseResponse getEnterprise(InetAddress ipAddress); + @Nullable + IspResponse getIsp(InetAddress ipAddress); + /** * Releases the current database object. Called after processing a single document. Databases should be closed or returned to a * resource pool. No further interactions should be expected. diff --git a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpProcessor.java b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpProcessor.java index 6898e44335793..8e7f5d575378d 100644 --- a/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpProcessor.java +++ b/modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpProcessor.java @@ -12,8 +12,12 @@ import com.maxmind.geoip2.model.AnonymousIpResponse; import com.maxmind.geoip2.model.AsnResponse; import com.maxmind.geoip2.model.CityResponse; +import com.maxmind.geoip2.model.ConnectionTypeResponse; +import com.maxmind.geoip2.model.ConnectionTypeResponse.ConnectionType; import com.maxmind.geoip2.model.CountryResponse; +import com.maxmind.geoip2.model.DomainResponse; import com.maxmind.geoip2.model.EnterpriseResponse; +import com.maxmind.geoip2.model.IspResponse; import com.maxmind.geoip2.record.City; import com.maxmind.geoip2.record.Continent; import com.maxmind.geoip2.record.Country; @@ -175,7 +179,10 @@ private Map getGeoData(GeoIpDatabase geoIpDatabase, String ip) t case Country -> retrieveCountryGeoData(geoIpDatabase, ipAddress); case Asn -> retrieveAsnGeoData(geoIpDatabase, ipAddress); case AnonymousIp -> retrieveAnonymousIpGeoData(geoIpDatabase, ipAddress); + case ConnectionType -> retrieveConnectionTypeGeoData(geoIpDatabase, ipAddress); + case Domain -> retrieveDomainGeoData(geoIpDatabase, ipAddress); case Enterprise -> retrieveEnterpriseGeoData(geoIpDatabase, ipAddress); + case Isp -> retrieveIspGeoData(geoIpDatabase, ipAddress); }; } @@ -317,7 +324,7 @@ private Map retrieveAsnGeoData(GeoIpDatabase geoIpDatabase, Inet return Map.of(); } Long asn = response.getAutonomousSystemNumber(); - String organization_name = response.getAutonomousSystemOrganization(); + String organizationName = response.getAutonomousSystemOrganization(); Network network = response.getNetwork(); Map geoData = new HashMap<>(); @@ -330,8 +337,8 @@ private Map retrieveAsnGeoData(GeoIpDatabase geoIpDatabase, Inet } } case ORGANIZATION_NAME -> { - if (organization_name != null) { - geoData.put("organization_name", organization_name); + if (organizationName != null) { + geoData.put("organization_name", organizationName); } } case NETWORK -> { @@ -384,6 +391,50 @@ private Map retrieveAnonymousIpGeoData(GeoIpDatabase geoIpDataba return geoData; } + private Map retrieveConnectionTypeGeoData(GeoIpDatabase geoIpDatabase, InetAddress ipAddress) { + ConnectionTypeResponse response = geoIpDatabase.getConnectionType(ipAddress); + if (response == null) { + return Map.of(); + } + + ConnectionType connectionType = response.getConnectionType(); + + Map geoData = new HashMap<>(); + for (Property property : this.properties) { + switch (property) { + case IP -> geoData.put("ip", NetworkAddress.format(ipAddress)); + case CONNECTION_TYPE -> { + if (connectionType != null) { + geoData.put("connection_type", connectionType.toString()); + } + } + } + } + return geoData; + } + + private Map retrieveDomainGeoData(GeoIpDatabase geoIpDatabase, InetAddress ipAddress) { + DomainResponse response = geoIpDatabase.getDomain(ipAddress); + if (response == null) { + return Map.of(); + } + + String domain = response.getDomain(); + + Map geoData = new HashMap<>(); + for (Property property : this.properties) { + switch (property) { + case IP -> geoData.put("ip", NetworkAddress.format(ipAddress)); + case DOMAIN -> { + if (domain != null) { + geoData.put("domain", domain); + } + } + } + } + return geoData; + } + private Map retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabase, InetAddress ipAddress) { EnterpriseResponse response = geoIpDatabase.getEnterprise(ipAddress); if (response == null) { @@ -397,9 +448,14 @@ private Map retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabas Subdivision subdivision = response.getMostSpecificSubdivision(); Long asn = response.getTraits().getAutonomousSystemNumber(); - String organization_name = response.getTraits().getAutonomousSystemOrganization(); + String organizationName = response.getTraits().getAutonomousSystemOrganization(); Network network = response.getTraits().getNetwork(); + String isp = response.getTraits().getIsp(); + String ispOrganization = response.getTraits().getOrganization(); + String mobileCountryCode = response.getTraits().getMobileCountryCode(); + String mobileNetworkCode = response.getTraits().getMobileNetworkCode(); + boolean isHostingProvider = response.getTraits().isHostingProvider(); boolean isTorExitNode = response.getTraits().isTorExitNode(); boolean isAnonymousVpn = response.getTraits().isAnonymousVpn(); @@ -407,6 +463,12 @@ private Map retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabas boolean isPublicProxy = response.getTraits().isPublicProxy(); boolean isResidentialProxy = response.getTraits().isResidentialProxy(); + String userType = response.getTraits().getUserType(); + + String domain = response.getTraits().getDomain(); + + ConnectionType connectionType = response.getTraits().getConnectionType(); + Map geoData = new HashMap<>(); for (Property property : this.properties) { switch (property) { @@ -473,8 +535,8 @@ private Map retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabas } } case ORGANIZATION_NAME -> { - if (organization_name != null) { - geoData.put("organization_name", organization_name); + if (organizationName != null) { + geoData.put("organization_name", organizationName); } } case NETWORK -> { @@ -500,6 +562,99 @@ private Map retrieveEnterpriseGeoData(GeoIpDatabase geoIpDatabas case RESIDENTIAL_PROXY -> { geoData.put("residential_proxy", isResidentialProxy); } + case DOMAIN -> { + if (domain != null) { + geoData.put("domain", domain); + } + } + case ISP -> { + if (isp != null) { + geoData.put("isp", isp); + } + } + case ISP_ORGANIZATION_NAME -> { + if (ispOrganization != null) { + geoData.put("isp_organization", ispOrganization); + } + } + case MOBILE_COUNTRY_CODE -> { + if (mobileCountryCode != null) { + geoData.put("mobile_country_code", mobileCountryCode); + } + } + case MOBILE_NETWORK_CODE -> { + if (mobileNetworkCode != null) { + geoData.put("mobile_network_code", mobileNetworkCode); + } + } + case USER_TYPE -> { + if (userType != null) { + geoData.put("user_type", userType); + } + } + case CONNECTION_TYPE -> { + if (connectionType != null) { + geoData.put("connection_type", connectionType.toString()); + } + } + } + } + return geoData; + } + + private Map retrieveIspGeoData(GeoIpDatabase geoIpDatabase, InetAddress ipAddress) { + IspResponse response = geoIpDatabase.getIsp(ipAddress); + if (response == null) { + return Map.of(); + } + + String isp = response.getIsp(); + String ispOrganization = response.getOrganization(); + String mobileNetworkCode = response.getMobileNetworkCode(); + String mobileCountryCode = response.getMobileCountryCode(); + Long asn = response.getAutonomousSystemNumber(); + String organizationName = response.getAutonomousSystemOrganization(); + Network network = response.getNetwork(); + + Map geoData = new HashMap<>(); + for (Property property : this.properties) { + switch (property) { + case IP -> geoData.put("ip", NetworkAddress.format(ipAddress)); + case ASN -> { + if (asn != null) { + geoData.put("asn", asn); + } + } + case ORGANIZATION_NAME -> { + if (organizationName != null) { + geoData.put("organization_name", organizationName); + } + } + case NETWORK -> { + if (network != null) { + geoData.put("network", network.toString()); + } + } + case ISP -> { + if (isp != null) { + geoData.put("isp", isp); + } + } + case ISP_ORGANIZATION_NAME -> { + if (ispOrganization != null) { + geoData.put("isp_organization", ispOrganization); + } + } + case MOBILE_COUNTRY_CODE -> { + if (mobileCountryCode != null) { + geoData.put("mobile_country_code", mobileCountryCode); + } + } + case MOBILE_NETWORK_CODE -> { + if (mobileNetworkCode != null) { + geoData.put("mobile_network_code", mobileNetworkCode); + } + } } } return geoData; diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java index ec77cacbdb6b6..6eb4e9b1acb51 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/GeoIpProcessorTests.java @@ -11,6 +11,7 @@ import com.maxmind.geoip2.DatabaseReader; import org.elasticsearch.common.CheckedSupplier; +import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.core.PathUtils; import org.elasticsearch.ingest.IngestDocument; import org.elasticsearch.ingest.RandomDocumentPicks; @@ -29,6 +30,7 @@ import static org.elasticsearch.ingest.IngestDocumentMatcher.assertIngestDocument; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; @@ -38,6 +40,24 @@ public class GeoIpProcessorTests extends ESTestCase { private static final Set ALL_PROPERTIES = Set.of(Property.values()); + public void testDatabasePropertyInvariants() { + // the city database is like a specialization of the country database + assertThat(Sets.difference(Database.Country.properties(), Database.City.properties()), is(empty())); + assertThat(Sets.difference(Database.Country.defaultProperties(), Database.City.defaultProperties()), is(empty())); + + // the isp database is like a specialization of the asn database + assertThat(Sets.difference(Database.Asn.properties(), Database.Isp.properties()), is(empty())); + assertThat(Sets.difference(Database.Asn.defaultProperties(), Database.Isp.defaultProperties()), is(empty())); + + // the enterprise database is like everything joined together + for (Database type : Database.values()) { + assertThat(Sets.difference(type.properties(), Database.Enterprise.properties()), is(empty())); + } + // but in terms of the default fields, it's like a drop-in replacement for the city database + // n.b. this is just a choice we decided to make here at Elastic + assertThat(Database.Enterprise.defaultProperties(), equalTo(Database.City.defaultProperties())); + } + public void testCity() throws Exception { GeoIpProcessor processor = new GeoIpProcessor( randomAlphaOfLength(10), @@ -336,8 +356,64 @@ public void testAnonymmousIp() throws Exception { assertThat(geoData.get("residential_proxy"), equalTo(true)); } + public void testConnectionType() throws Exception { + String ip = "214.78.120.5"; + GeoIpProcessor processor = new GeoIpProcessor( + randomAlphaOfLength(10), + null, + "source_field", + loader("/GeoIP2-Connection-Type-Test.mmdb"), + () -> true, + "target_field", + ALL_PROPERTIES, + false, + false, + "filename" + ); + + Map document = new HashMap<>(); + document.put("source_field", ip); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + processor.execute(ingestDocument); + + assertThat(ingestDocument.getSourceAndMetadata().get("source_field"), equalTo(ip)); + @SuppressWarnings("unchecked") + Map geoData = (Map) ingestDocument.getSourceAndMetadata().get("target_field"); + assertThat(geoData.size(), equalTo(2)); + assertThat(geoData.get("ip"), equalTo(ip)); + assertThat(geoData.get("connection_type"), equalTo("Satellite")); + } + + public void testDomain() throws Exception { + String ip = "69.219.64.2"; + GeoIpProcessor processor = new GeoIpProcessor( + randomAlphaOfLength(10), + null, + "source_field", + loader("/GeoIP2-Domain-Test.mmdb"), + () -> true, + "target_field", + ALL_PROPERTIES, + false, + false, + "filename" + ); + + Map document = new HashMap<>(); + document.put("source_field", ip); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + processor.execute(ingestDocument); + + assertThat(ingestDocument.getSourceAndMetadata().get("source_field"), equalTo(ip)); + @SuppressWarnings("unchecked") + Map geoData = (Map) ingestDocument.getSourceAndMetadata().get("target_field"); + assertThat(geoData.size(), equalTo(2)); + assertThat(geoData.get("ip"), equalTo(ip)); + assertThat(geoData.get("domain"), equalTo("ameritech.net")); + } + public void testEnterprise() throws Exception { - String ip = "2.125.160.216"; + String ip = "74.209.24.4"; GeoIpProcessor processor = new GeoIpProcessor( randomAlphaOfLength(10), null, @@ -359,26 +435,67 @@ public void testEnterprise() throws Exception { assertThat(ingestDocument.getSourceAndMetadata().get("source_field"), equalTo(ip)); @SuppressWarnings("unchecked") Map geoData = (Map) ingestDocument.getSourceAndMetadata().get("target_field"); - assertThat(geoData.size(), equalTo(16)); + assertThat(geoData.size(), equalTo(23)); assertThat(geoData.get("ip"), equalTo(ip)); - assertThat(geoData.get("country_iso_code"), equalTo("GB")); - assertThat(geoData.get("country_name"), equalTo("United Kingdom")); - assertThat(geoData.get("continent_name"), equalTo("Europe")); - assertThat(geoData.get("region_iso_code"), equalTo("GB-WBK")); - assertThat(geoData.get("region_name"), equalTo("West Berkshire")); - assertThat(geoData.get("city_name"), equalTo("Boxford")); - assertThat(geoData.get("timezone"), equalTo("Europe/London")); + assertThat(geoData.get("country_iso_code"), equalTo("US")); + assertThat(geoData.get("country_name"), equalTo("United States")); + assertThat(geoData.get("continent_name"), equalTo("North America")); + assertThat(geoData.get("region_iso_code"), equalTo("US-NY")); + assertThat(geoData.get("region_name"), equalTo("New York")); + assertThat(geoData.get("city_name"), equalTo("Chatham")); + assertThat(geoData.get("timezone"), equalTo("America/New_York")); Map location = new HashMap<>(); - location.put("lat", 51.75); - location.put("lon", -1.25); + location.put("lat", 42.3478); + location.put("lon", -73.5549); assertThat(geoData.get("location"), equalTo(location)); - assertThat(geoData.get("network"), equalTo("2.125.160.216/29")); + assertThat(geoData.get("asn"), equalTo(14671L)); + assertThat(geoData.get("organization_name"), equalTo("FairPoint Communications")); + assertThat(geoData.get("network"), equalTo("74.209.16.0/20")); assertThat(geoData.get("hosting_provider"), equalTo(false)); assertThat(geoData.get("tor_exit_node"), equalTo(false)); assertThat(geoData.get("anonymous_vpn"), equalTo(false)); assertThat(geoData.get("anonymous"), equalTo(false)); assertThat(geoData.get("public_proxy"), equalTo(false)); assertThat(geoData.get("residential_proxy"), equalTo(false)); + assertThat(geoData.get("domain"), equalTo("frpt.net")); + assertThat(geoData.get("isp"), equalTo("Fairpoint Communications")); + assertThat(geoData.get("isp_organization"), equalTo("Fairpoint Communications")); + assertThat(geoData.get("user_type"), equalTo("residential")); + assertThat(geoData.get("connection_type"), equalTo("Cable/DSL")); + } + + public void testIsp() throws Exception { + String ip = "149.101.100.1"; + GeoIpProcessor processor = new GeoIpProcessor( + randomAlphaOfLength(10), + null, + "source_field", + loader("/GeoIP2-ISP-Test.mmdb"), + () -> true, + "target_field", + ALL_PROPERTIES, + false, + false, + "filename" + ); + + Map document = new HashMap<>(); + document.put("source_field", ip); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + processor.execute(ingestDocument); + + assertThat(ingestDocument.getSourceAndMetadata().get("source_field"), equalTo(ip)); + @SuppressWarnings("unchecked") + Map geoData = (Map) ingestDocument.getSourceAndMetadata().get("target_field"); + assertThat(geoData.size(), equalTo(8)); + assertThat(geoData.get("ip"), equalTo(ip)); + assertThat(geoData.get("asn"), equalTo(6167L)); + assertThat(geoData.get("organization_name"), equalTo("CELLCO-PART")); + assertThat(geoData.get("network"), equalTo("149.101.100.0/28")); + assertThat(geoData.get("isp"), equalTo("Verizon Wireless")); + assertThat(geoData.get("isp_organization"), equalTo("Verizon Wireless")); + assertThat(geoData.get("mobile_network_code"), equalTo("004")); + assertThat(geoData.get("mobile_country_code"), equalTo("310")); } public void testAddressIsNotInTheDatabase() throws Exception { diff --git a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java index 4e6e1d11c0fdd..a465ae7cd799d 100644 --- a/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/MaxMindSupportTests.java @@ -153,6 +153,9 @@ public class MaxMindSupportTests extends ESTestCase { "traits.userType" ); + private static final Set CONNECT_TYPE_SUPPORTED_FIELDS = Set.of("connectionType"); + private static final Set CONNECT_TYPE_UNSUPPORTED_FIELDS = Set.of("ipAddress", "network"); + private static final Set COUNTRY_SUPPORTED_FIELDS = Set.of("continent.name", "country.isoCode", "country.name"); private static final Set COUNTRY_UNSUPPORTED_FIELDS = Set.of( "continent.code", @@ -201,6 +204,9 @@ public class MaxMindSupportTests extends ESTestCase { "traits.userType" ); + private static final Set DOMAIN_SUPPORTED_FIELDS = Set.of("domain"); + private static final Set DOMAIN_UNSUPPORTED_FIELDS = Set.of("ipAddress", "network"); + private static final Set ENTERPRISE_SUPPORTED_FIELDS = Set.of( "city.name", "continent.name", @@ -215,11 +221,18 @@ public class MaxMindSupportTests extends ESTestCase { "traits.anonymousVpn", "traits.autonomousSystemNumber", "traits.autonomousSystemOrganization", + "traits.connectionType", + "traits.domain", "traits.hostingProvider", + "traits.isp", + "traits.mobileCountryCode", + "traits.mobileNetworkCode", "traits.network", + "traits.organization", "traits.publicProxy", "traits.residentialProxy", - "traits.torExitNode" + "traits.torExitNode", + "traits.userType" ); private static final Set ENTERPRISE_UNSUPPORTED_FIELDS = Set.of( "city.confidence", @@ -267,20 +280,25 @@ public class MaxMindSupportTests extends ESTestCase { "subdivisions.names", "traits.anonymousProxy", "traits.anycast", - "traits.connectionType", - "traits.domain", "traits.ipAddress", - "traits.isp", "traits.legitimateProxy", - "traits.mobileCountryCode", - "traits.mobileNetworkCode", - "traits.organization", "traits.satelliteProvider", "traits.staticIpScore", - "traits.userCount", - "traits.userType" + "traits.userCount" + ); + + private static final Set ISP_SUPPORTED_FIELDS = Set.of( + "autonomousSystemNumber", + "autonomousSystemOrganization", + "network", + "isp", + "mobileCountryCode", + "mobileNetworkCode", + "organization" ); + private static final Set ISP_UNSUPPORTED_FIELDS = Set.of("ipAddress"); + private static final Map> TYPE_TO_SUPPORTED_FIELDS_MAP = Map.of( Database.AnonymousIp, ANONYMOUS_IP_SUPPORTED_FIELDS, @@ -288,10 +306,16 @@ public class MaxMindSupportTests extends ESTestCase { ASN_SUPPORTED_FIELDS, Database.City, CITY_SUPPORTED_FIELDS, + Database.ConnectionType, + CONNECT_TYPE_SUPPORTED_FIELDS, Database.Country, COUNTRY_SUPPORTED_FIELDS, + Database.Domain, + DOMAIN_SUPPORTED_FIELDS, Database.Enterprise, - ENTERPRISE_SUPPORTED_FIELDS + ENTERPRISE_SUPPORTED_FIELDS, + Database.Isp, + ISP_SUPPORTED_FIELDS ); private static final Map> TYPE_TO_UNSUPPORTED_FIELDS_MAP = Map.of( Database.AnonymousIp, @@ -300,10 +324,16 @@ public class MaxMindSupportTests extends ESTestCase { ASN_UNSUPPORTED_FIELDS, Database.City, CITY_UNSUPPORTED_FIELDS, + Database.ConnectionType, + CONNECT_TYPE_UNSUPPORTED_FIELDS, Database.Country, COUNTRY_UNSUPPORTED_FIELDS, + Database.Domain, + DOMAIN_UNSUPPORTED_FIELDS, Database.Enterprise, - ENTERPRISE_UNSUPPORTED_FIELDS + ENTERPRISE_UNSUPPORTED_FIELDS, + Database.Isp, + ISP_UNSUPPORTED_FIELDS ); private static final Map> TYPE_TO_MAX_MIND_CLASS = Map.of( Database.AnonymousIp, @@ -312,18 +342,19 @@ public class MaxMindSupportTests extends ESTestCase { AsnResponse.class, Database.City, CityResponse.class, + Database.ConnectionType, + ConnectionTypeResponse.class, Database.Country, CountryResponse.class, + Database.Domain, + DomainResponse.class, Database.Enterprise, - EnterpriseResponse.class + EnterpriseResponse.class, + Database.Isp, + IspResponse.class ); - private static final Set> KNOWN_UNSUPPORTED_RESPONSE_CLASSES = Set.of( - ConnectionTypeResponse.class, - DomainResponse.class, - IspResponse.class, - IpRiskResponse.class - ); + private static final Set> KNOWN_UNSUPPORTED_RESPONSE_CLASSES = Set.of(IpRiskResponse.class); public void testMaxMindSupport() { for (Database databaseType : Database.values()) { diff --git a/modules/ingest-geoip/src/test/resources/GeoIP2-Connection-Type-Test.mmdb b/modules/ingest-geoip/src/test/resources/GeoIP2-Connection-Type-Test.mmdb new file mode 100644 index 0000000000000..7bfae78964df0 Binary files /dev/null and b/modules/ingest-geoip/src/test/resources/GeoIP2-Connection-Type-Test.mmdb differ diff --git a/modules/ingest-geoip/src/test/resources/GeoIP2-Domain-Test.mmdb b/modules/ingest-geoip/src/test/resources/GeoIP2-Domain-Test.mmdb new file mode 100644 index 0000000000000..d21c2a93df7d4 Binary files /dev/null and b/modules/ingest-geoip/src/test/resources/GeoIP2-Domain-Test.mmdb differ diff --git a/modules/ingest-geoip/src/test/resources/GeoIP2-ISP-Test.mmdb b/modules/ingest-geoip/src/test/resources/GeoIP2-ISP-Test.mmdb new file mode 100644 index 0000000000000..d16b0eee4c5e5 Binary files /dev/null and b/modules/ingest-geoip/src/test/resources/GeoIP2-ISP-Test.mmdb differ diff --git a/modules/kibana/src/internalClusterTest/java/org/elasticsearch/kibana/KibanaThreadPoolIT.java b/modules/kibana/src/internalClusterTest/java/org/elasticsearch/kibana/KibanaThreadPoolIT.java index 275666eec5c42..b48b2941e6097 100644 --- a/modules/kibana/src/internalClusterTest/java/org/elasticsearch/kibana/KibanaThreadPoolIT.java +++ b/modules/kibana/src/internalClusterTest/java/org/elasticsearch/kibana/KibanaThreadPoolIT.java @@ -8,6 +8,8 @@ package org.elasticsearch.kibana; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchRequest; @@ -15,12 +17,15 @@ import org.elasticsearch.client.internal.Client; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; +import org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor; import org.elasticsearch.index.IndexingPressure; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.threadpool.ThreadPool; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Locale; @@ -42,7 +47,12 @@ * threads that wait on a phaser. This lets us verify that operations on system indices * are being directed to other thread pools.

*/ +@TestLogging( + reason = "investigate", + value = "org.elasticsearch.kibana.KibanaThreadPoolIT:DEBUG,org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor:TRACE" +) public class KibanaThreadPoolIT extends ESIntegTestCase { + private static final Logger logger = LogManager.getLogger(KibanaThreadPoolIT.class); @Override protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) { @@ -195,10 +205,21 @@ private static void fillThreadPoolQueues(String threadPoolName, ThreadPool threa try { threadPool.executor(threadPoolName).execute(() -> {}); } catch (EsRejectedExecutionException e) { + logger.debug("Exception when filling the queue " + threadPoolName, e); + logThreadPoolQueue(threadPoolName, threadPool); // we can't be sure that some other task won't get queued in a test cluster // but the threadpool's thread is already blocked } } + + logThreadPoolQueue(threadPoolName, threadPool); + } + + private static void logThreadPoolQueue(String threadPoolName, ThreadPool threadPool) { + if (threadPool.executor(threadPoolName) instanceof EsThreadPoolExecutor tpe) { + logger.debug("Thread pool details " + threadPoolName + " " + tpe); + logger.debug(Arrays.toString(tpe.getTasks().toArray())); + } } } diff --git a/muted-tests.yml b/muted-tests.yml index 210215a131339..e1e80a3d3459b 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -1,6 +1,9 @@ tests: - class: "org.elasticsearch.xpack.transform.transforms.scheduling.MonotonicClockTests" issue: "https://github.com/elastic/elasticsearch/issues/108529" +- class: "org.elasticsearch.xpack.inference.action.filter.ShardBulkInferenceActionFilterTests" + issue: "https://github.com/elastic/elasticsearch/issues/108649" + method: "testManyRandomDocs" # Examples: # # Mute a single test case in a YAML test suite: diff --git a/qa/packaging/src/test/java/org/elasticsearch/packaging/test/DockerTests.java b/qa/packaging/src/test/java/org/elasticsearch/packaging/test/DockerTests.java index 81ac8ab1200f6..f9723f30cc371 100644 --- a/qa/packaging/src/test/java/org/elasticsearch/packaging/test/DockerTests.java +++ b/qa/packaging/src/test/java/org/elasticsearch/packaging/test/DockerTests.java @@ -126,6 +126,13 @@ public void teardownTest() { rm(tempDir); } + @Override + protected void dumpDebug() { + final Result containerLogs = getContainerLogs(); + logger.warn("Elasticsearch log stdout:\n" + containerLogs.stdout()); + logger.warn("Elasticsearch log stderr:\n" + containerLogs.stderr()); + } + /** * Checks that the Docker image can be run, and that it passes various checks. */ @@ -1220,7 +1227,8 @@ public void test500Readiness() throws Exception { ); waitForElasticsearch(installation); dumpDebug(); - assertTrue(readinessProbe(9399)); + // readiness may still take time as file settings are applied into cluster state (even non-existent file settings) + assertBusy(() -> assertTrue(readinessProbe(9399))); } @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/99508") diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/capabilities/10_basic.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/capabilities/10_basic.yml index 715e696bd1032..ad70ad7f8fb1e 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/capabilities/10_basic.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/capabilities/10_basic.yml @@ -2,6 +2,7 @@ "Capabilities API": - requires: + test_runner_features: [capabilities] capabilities: - method: GET path: /_capabilities diff --git a/server/src/internalClusterTest/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java b/server/src/internalClusterTest/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java index 0b8ee5ea82601..919d548d6498d 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java @@ -250,17 +250,16 @@ public void testRolloverDryRun() throws Exception { ensureGreen(); Logger allocationServiceLogger = LogManager.getLogger(AllocationService.class); - MockLogAppender appender = new MockLogAppender(); - appender.addExpectation( - new MockLogAppender.UnseenEventExpectation( - "no related message logged on dry run", - AllocationService.class.getName(), - Level.INFO, - "*test_index*" - ) - ); final RolloverResponse response; - try (var ignored = appender.capturing(AllocationService.class)) { + try (var appender = MockLogAppender.capture(AllocationService.class)) { + appender.addExpectation( + new MockLogAppender.UnseenEventExpectation( + "no related message logged on dry run", + AllocationService.class.getName(), + Level.INFO, + "*test_index*" + ) + ); response = indicesAdmin().prepareRolloverIndex("test_alias").dryRun(true).get(); appender.assertAllExpectationsMatched(); } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/discovery/ClusterDisruptionIT.java b/server/src/internalClusterTest/java/org/elasticsearch/discovery/ClusterDisruptionIT.java index e36d7a4e56eab..7f94809e64fa6 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/discovery/ClusterDisruptionIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/discovery/ClusterDisruptionIT.java @@ -40,7 +40,7 @@ import org.elasticsearch.test.disruption.NetworkDisruption.Bridge; import org.elasticsearch.test.disruption.NetworkDisruption.TwoPartitions; import org.elasticsearch.test.disruption.ServiceDisruptionScheme; -import org.elasticsearch.test.junit.annotations.TestIssueLogging; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.xcontent.XContentType; @@ -94,17 +94,17 @@ static ConflictMode randomMode() { } /** - * Test that we do not loose document whose indexing request was successful, under a randomly selected disruption scheme + * Test that we do not lose documents, indexed via requests that return success, under randomly selected disruption schemes. * We also collect & report the type of indexing failures that occur. *

- * This test is a superset of tests run in the Jepsen test suite, with the exception of versioned updates + * This test is a superset of tests run in the Jepsen test suite, with the exception of versioned updates. */ - @TestIssueLogging( + @TestLogging( value = "_root:DEBUG,org.elasticsearch.action.bulk:TRACE,org.elasticsearch.action.get:TRACE," + "org.elasticsearch.discovery:TRACE,org.elasticsearch.action.support.replication:TRACE," + "org.elasticsearch.cluster.service:TRACE,org.elasticsearch.indices.recovery:TRACE," + "org.elasticsearch.indices.cluster:TRACE,org.elasticsearch.index.shard:TRACE", - issueUrl = "https://github.com/elastic/elasticsearch/issues/41068" + reason = "Past failures have required a lot of additional logging to debug" ) public void testAckedIndexing() throws Exception { diff --git a/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java b/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java index 407b1aae40600..09dd564d864db 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java @@ -102,23 +102,6 @@ public Path nodeConfigPath(int nodeOrdinal) { } public void testCannotJoinNodeWithSingleNodeDiscovery() throws Exception { - MockLogAppender mockAppender = new MockLogAppender(); - mockAppender.addExpectation( - new MockLogAppender.SeenEventExpectation("test", JoinHelper.class.getCanonicalName(), Level.INFO, "failed to join") { - - @Override - public boolean innerMatch(final LogEvent event) { - return event.getThrown() != null - && event.getThrown().getClass() == RemoteTransportException.class - && event.getThrown().getCause() != null - && event.getThrown().getCause().getClass() == IllegalStateException.class - && event.getThrown() - .getCause() - .getMessage() - .contains("cannot join node with [discovery.type] set to [single-node]"); - } - } - ); final TransportService service = internalCluster().getInstance(TransportService.class); final int port = service.boundAddress().publishAddress().getPort(); final NodeConfigurationSource configurationSource = new NodeConfigurationSource() { @@ -155,7 +138,24 @@ public Path nodeConfigPath(int nodeOrdinal) { Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class), Function.identity() ); - try (var ignored = mockAppender.capturing(JoinHelper.class)) { + try (var mockAppender = MockLogAppender.capture(JoinHelper.class)) { + mockAppender.addExpectation( + new MockLogAppender.SeenEventExpectation("test", JoinHelper.class.getCanonicalName(), Level.INFO, "failed to join") { + + @Override + public boolean innerMatch(final LogEvent event) { + return event.getThrown() != null + && event.getThrown().getClass() == RemoteTransportException.class + && event.getThrown().getCause() != null + && event.getThrown().getCause().getClass() == IllegalStateException.class + && event.getThrown() + .getCause() + .getMessage() + .contains("cannot join node with [discovery.type] set to [single-node]"); + } + } + ); + other.beforeTest(random()); final ClusterState first = internalCluster().getInstance(ClusterService.class).state(); assertThat(first.nodes().getSize(), equalTo(1)); diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java index 3f7ed48b714fb..b9850bc95275c 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -49,6 +49,7 @@ import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.engine.NoOpEngine; import org.elasticsearch.index.flush.FlushStats; +import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.SourceToParse; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; @@ -633,7 +634,8 @@ public static final IndexShard newIndexShard( cbs, IndexModule.DEFAULT_SNAPSHOT_COMMIT_SUPPLIER, System::nanoTime, - null + null, + MapperMetrics.NOOP ); } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/indices/cluster/ShardLockFailureIT.java b/server/src/internalClusterTest/java/org/elasticsearch/indices/cluster/ShardLockFailureIT.java index 488641c853562..215596c8130be 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/indices/cluster/ShardLockFailureIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/indices/cluster/ShardLockFailureIT.java @@ -68,10 +68,9 @@ public void testShardLockFailure() throws Exception { } }); - var mockLogAppender = new MockLogAppender(); try ( var ignored1 = internalCluster().getInstance(NodeEnvironment.class, node).shardLock(shardId, "blocked for test"); - var ignored2 = mockLogAppender.capturing(IndicesClusterStateService.class); + var mockLogAppender = MockLogAppender.capture(IndicesClusterStateService.class); ) { final CountDownLatch countDownLatch = new CountDownLatch(1); @@ -138,10 +137,9 @@ public void testShardLockTimeout() throws Exception { final var shardId = new ShardId(resolveIndex(indexName), 0); - var mockLogAppender = new MockLogAppender(); try ( var ignored1 = internalCluster().getInstance(NodeEnvironment.class, node).shardLock(shardId, "blocked for test"); - var ignored2 = mockLogAppender.capturing(IndicesClusterStateService.class); + var mockLogAppender = MockLogAppender.capture(IndicesClusterStateService.class); ) { mockLogAppender.addExpectation( new MockLogAppender.SeenEventExpectation( diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java index 4446338c4ff2a..2a58ef8eab3bc 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java @@ -31,7 +31,6 @@ import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.core.Releasable; import org.elasticsearch.index.IndexMode; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.IndexSettings; @@ -649,8 +648,7 @@ public void testManyIndicesWithSameMapping() { reason = "verify the log output on cancelled" ) public void testCancel() throws Exception { - MockLogAppender logAppender = new MockLogAppender(); - try (Releasable ignored = logAppender.capturing(TransportFieldCapabilitiesAction.class)) { + try (var logAppender = MockLogAppender.capture(TransportFieldCapabilitiesAction.class)) { logAppender.addExpectation( new MockLogAppender.SeenEventExpectation( "clear resources", diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java index 52d0fea8806a6..aa47663ad3886 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/DedicatedClusterSnapshotRestoreIT.java @@ -1264,11 +1264,10 @@ public void testDeleteSnapshotsOfDifferentIndexSets() throws IllegalAccessExcept final String repoName = "test-repo"; createRepository(repoName, "fs"); - final MockLogAppender mockAppender = new MockLogAppender(); - mockAppender.addExpectation( - new MockLogAppender.UnseenEventExpectation("no warnings", BlobStoreRepository.class.getCanonicalName(), Level.WARN, "*") - ); - try (var ignored = mockAppender.capturing(BlobStoreRepository.class)) { + try (var mockAppender = MockLogAppender.capture(BlobStoreRepository.class)) { + mockAppender.addExpectation( + new MockLogAppender.UnseenEventExpectation("no warnings", BlobStoreRepository.class.getCanonicalName(), Level.WARN, "*") + ); final String index1 = "index-1"; final String index2 = "index-2"; createIndexWithContent("index-1"); diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/RestoreSnapshotIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/RestoreSnapshotIT.java index aa0b1edaafd6c..c9d77d7e41f16 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/RestoreSnapshotIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/RestoreSnapshotIT.java @@ -162,8 +162,7 @@ public void testParallelRestoreOperationsFromSingleSnapshot() throws Exception { value = "org.elasticsearch.snapshots.RestoreService:INFO" ) public void testRestoreLogging() throws IllegalAccessException { - final MockLogAppender mockLogAppender = new MockLogAppender(); - try (var ignored = mockLogAppender.capturing(RestoreService.class)) { + try (var mockLogAppender = MockLogAppender.capture(RestoreService.class)) { String indexName = "testindex"; String repoName = "test-restore-snapshot-repo"; String snapshotName = "test-restore-snapshot"; @@ -899,8 +898,7 @@ public void testNoWarningsOnRestoreOverClosedIndex() throws IllegalAccessExcepti index(indexName, "some_id", Map.of("foo", "bar")); assertAcked(indicesAdmin().prepareClose(indexName).get()); - final MockLogAppender mockAppender = new MockLogAppender(); - try (var ignored = mockAppender.capturing(FileRestoreContext.class)) { + try (var mockAppender = MockLogAppender.capture(FileRestoreContext.class)) { mockAppender.addExpectation( new MockLogAppender.UnseenEventExpectation("no warnings", FileRestoreContext.class.getCanonicalName(), Level.WARN, "*") ); diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotThrottlingIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotThrottlingIT.java index 9aec3504a65f5..40dc9cbf6ff9f 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotThrottlingIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotThrottlingIT.java @@ -136,9 +136,7 @@ public void testWarningSpeedOverRecovery() throws Exception { } final String primaryNode = internalCluster().startNode(primaryNodeSettings); - final MockLogAppender mockLogAppender = new MockLogAppender(); - try (var ignored = mockLogAppender.capturing(BlobStoreRepository.class)) { - + try (var mockLogAppender = MockLogAppender.capture(BlobStoreRepository.class)) { MockLogAppender.EventuallySeenEventExpectation snapshotExpectation = new MockLogAppender.EventuallySeenEventExpectation( "snapshot speed over recovery speed", "org.elasticsearch.repositories.blobstore.BlobStoreRepository", diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotsServiceIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotsServiceIT.java index 19e66c6653577..307219bcc667e 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotsServiceIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/SnapshotsServiceIT.java @@ -31,9 +31,7 @@ public void testDeletingSnapshotsIsLoggedAfterClusterStateIsProcessed() throws E createIndexWithRandomDocs("test-index", randomIntBetween(1, 42)); createSnapshot("test-repo", "test-snapshot", List.of("test-index")); - final MockLogAppender mockLogAppender = new MockLogAppender(); - - try (var ignored = mockLogAppender.capturing(SnapshotsService.class)) { + try (var mockLogAppender = MockLogAppender.capture(SnapshotsService.class)) { mockLogAppender.addExpectation( new MockLogAppender.UnseenEventExpectation( "[does-not-exist]", @@ -80,10 +78,7 @@ public void testSnapshotDeletionFailureShouldBeLogged() throws Exception { createIndexWithRandomDocs("test-index", randomIntBetween(1, 42)); createSnapshot("test-repo", "test-snapshot", List.of("test-index")); - final MockLogAppender mockLogAppender = new MockLogAppender(); - - try (var ignored = mockLogAppender.capturing(SnapshotsService.class)) { - + try (var mockLogAppender = MockLogAppender.capture(SnapshotsService.class)) { mockLogAppender.addExpectation( new MockLogAppender.SeenEventExpectation( "[test-snapshot]", diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index f1232d2442c8b..3fc1af3f4c3c7 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -165,6 +165,10 @@ static TransportVersion def(int id) { public static final TransportVersion JOIN_STATUS_AGE_SERIALIZATION = def(8_656_00_0); public static final TransportVersion ML_RERANK_DOC_OPTIONAL = def(8_657_00_0); public static final TransportVersion FAILURE_STORE_FIELD_PARITY = def(8_658_00_0); + public static final TransportVersion ML_INFERENCE_AZURE_AI_STUDIO = def(8_659_00_0); + public static final TransportVersion ML_INFERENCE_COHERE_COMPLETION_ADDED = def(8_660_00_0); + public static final TransportVersion ESQL_REMOVE_ES_SOURCE_OPTIONS = def(8_661_00_0); + /* * STOP! READ THIS FIRST! No, really, * ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _ diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index ab93f98c5648b..f08dde7c5ba94 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -807,6 +807,7 @@ private static ActionFilters setupActionFilters(List actionPlugins finalFilters.add(filter); } } + mappedFilters.addAll(plugin.getMappedActionFilters()); } if (mappedFilters.isEmpty() == false) { finalFilters.add(new MappedActionFilters(mappedFilters)); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java index 5d20443fa3989..c2fd49eb91a42 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/create/CreateSnapshotRequest.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.cluster.metadata.DataStream; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; @@ -65,7 +66,9 @@ public class CreateSnapshotRequest extends MasterNodeRequest

  • * Now you can run all of the ESQL tests like CI: - * {@code ./gradlew -p x-pack/plugin/esql/ check} + * {@code ./gradlew -p x-pack/plugin/esql/ test} *
  • *
  • - * Now it's time to write some docs! Open {@code docs/reference/esql/esql-functions-operators.asciidoc} - * and add your function in alphabetical order to the list at the top and then add it to - * the includes below. - *
  • - *
  • - * Now go make a file to include. You can start by copying one of it's neighbors. - *
  • - *
  • - * It's important that any examples you add to the docs be included from the csv-spec file. - * That looks like: - *
    {@code
    - * [source.merge.styled,esql]
    - * ----
    - * include::{esql-specs}/math.csv-spec[tag=mv_min]
    - * ----
    - * [%header.monospaced.styled,format=dsv,separator=|]
    - * |===
    - * include::{esql-specs}/math.csv-spec[tag=mv_min-result]
    - * |===
    - *         }
    - * This includes the bit of the csv-spec file fenced by {@code // tag::mv_min[]}. You'll - * want a fence descriptive for your function. Consider the non-includes lines to be - * asciidoc ceremony to make the result look right in the rendered docs. - *
  • - *
  • - * Generate a syntax diagram and a table with supported types by running the tests via + * Now it's time to write some docs! + * Generate the docs, a syntax diagram and a table with supported types by running the tests via * gradle: {@code ./gradlew x-pack:plugin:esql:test} * The generated files are *
      diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java index 4a5748f26a07f..385058c520393 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.esql.io.stream; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.TransportVersion; import org.elasticsearch.TransportVersions; import org.elasticsearch.common.TriFunction; import org.elasticsearch.common.io.stream.NamedWriteable; @@ -67,6 +68,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.math.Asin; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Atan2; +import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cbrt; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Ceil; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cos; import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cosh; @@ -192,7 +194,6 @@ import org.elasticsearch.xpack.ql.expression.predicate.regex.RegexMatch; import org.elasticsearch.xpack.ql.expression.predicate.regex.WildcardPattern; import org.elasticsearch.xpack.ql.index.EsIndex; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.plan.logical.Filter; import org.elasticsearch.xpack.ql.plan.logical.Limit; import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan; @@ -344,6 +345,7 @@ public static List namedTypeEntries() { of(ESQL_UNARY_SCLR_CLS, Acos.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Asin.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Atan.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), + of(ESQL_UNARY_SCLR_CLS, Cbrt.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Ceil.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Cos.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), of(ESQL_UNARY_SCLR_CLS, Cosh.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar), @@ -794,11 +796,11 @@ static EsRelation readEsRelation(PlanStreamInput in) throws IOException { Source source = in.readSource(); EsIndex esIndex = readEsIndex(in); List attributes = readAttributes(in); - EsSourceOptions esSourceOptions = in.getTransportVersion().onOrAfter(TransportVersions.ESQL_ES_SOURCE_OPTIONS) - ? new EsSourceOptions(in) - : EsSourceOptions.NO_OPTIONS; + if (supportingEsSourceOptions(in.getTransportVersion())) { + readEsSourceOptions(in); // consume optional strings sent by remote + } boolean frozen = in.readBoolean(); - return new EsRelation(source, esIndex, attributes, esSourceOptions, frozen); + return new EsRelation(source, esIndex, attributes, frozen); } static void writeEsRelation(PlanStreamOutput out, EsRelation relation) throws IOException { @@ -806,12 +808,35 @@ static void writeEsRelation(PlanStreamOutput out, EsRelation relation) throws IO out.writeNoSource(); writeEsIndex(out, relation.index()); writeAttributes(out, relation.output()); - if (out.getTransportVersion().onOrAfter(TransportVersions.ESQL_ES_SOURCE_OPTIONS)) { - relation.esSourceOptions().writeEsSourceOptions(out); + if (supportingEsSourceOptions(out.getTransportVersion())) { + writeEsSourceOptions(out); // write (null) string fillers expected by remote } out.writeBoolean(relation.frozen()); } + private static boolean supportingEsSourceOptions(TransportVersion version) { + return version.onOrAfter(TransportVersions.ESQL_ES_SOURCE_OPTIONS) + && version.before(TransportVersions.ESQL_REMOVE_ES_SOURCE_OPTIONS); + } + + private static void readEsSourceOptions(PlanStreamInput in) throws IOException { + // allowNoIndices + in.readOptionalString(); + // ignoreUnavailable + in.readOptionalString(); + // preference + in.readOptionalString(); + } + + private static void writeEsSourceOptions(PlanStreamOutput out) throws IOException { + // allowNoIndices + out.writeOptionalString(null); + // ignoreUnavailable + out.writeOptionalString(null); + // preference + out.writeOptionalString(null); + } + static Eval readEval(PlanStreamInput in) throws IOException { return new Eval(in.readSource(), in.readLogicalPlanNode(), readAliases(in)); } @@ -1289,6 +1314,7 @@ static void writeBinaryLogic(PlanStreamOutput out, BinaryLogic binaryLogic) thro entry(name(Acos.class), Acos::new), entry(name(Asin.class), Asin::new), entry(name(Atan.class), Atan::new), + entry(name(Cbrt.class), Cbrt::new), entry(name(Ceil.class), Ceil::new), entry(name(Cos.class), Cos::new), entry(name(Cosh.class), Cosh::new), 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 899f745e50c3a..38693348204af 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 @@ -73,7 +73,6 @@ null null null null -'options' 'metadata' null null @@ -193,7 +192,6 @@ QUOTED_IDENTIFIER EXPR_LINE_COMMENT EXPR_MULTILINE_COMMENT EXPR_WS -OPTIONS METADATA FROM_LINE_COMMENT FROM_MULTILINE_COMMENT @@ -332,7 +330,6 @@ FROM_CLOSING_BRACKET FROM_COMMA FROM_ASSIGN FROM_QUOTED_STRING -OPTIONS METADATA FROM_INDEX_UNQUOTED_IDENTIFIER FROM_LINE_COMMENT @@ -435,4 +432,4 @@ METRICS_MODE CLOSING_METRICS_MODE atn: -[4, 0, 117, 1307, 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, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 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, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 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, 13, 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, 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, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 4, 19, 524, 8, 19, 11, 19, 12, 19, 525, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 534, 8, 20, 10, 20, 12, 20, 537, 9, 20, 1, 20, 3, 20, 540, 8, 20, 1, 20, 3, 20, 543, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 552, 8, 21, 10, 21, 12, 21, 555, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 4, 22, 563, 8, 22, 11, 22, 12, 22, 564, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 3, 23, 572, 8, 23, 1, 24, 4, 24, 575, 8, 24, 11, 24, 12, 24, 576, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 3, 35, 616, 8, 35, 1, 35, 4, 35, 619, 8, 35, 11, 35, 12, 35, 620, 1, 36, 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 3, 38, 630, 8, 38, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 3, 40, 637, 8, 40, 1, 41, 1, 41, 1, 41, 5, 41, 642, 8, 41, 10, 41, 12, 41, 645, 9, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 5, 41, 653, 8, 41, 10, 41, 12, 41, 656, 9, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 663, 8, 41, 1, 41, 3, 41, 666, 8, 41, 3, 41, 668, 8, 41, 1, 42, 4, 42, 671, 8, 42, 11, 42, 12, 42, 672, 1, 43, 4, 43, 676, 8, 43, 11, 43, 12, 43, 677, 1, 43, 1, 43, 5, 43, 682, 8, 43, 10, 43, 12, 43, 685, 9, 43, 1, 43, 1, 43, 4, 43, 689, 8, 43, 11, 43, 12, 43, 690, 1, 43, 4, 43, 694, 8, 43, 11, 43, 12, 43, 695, 1, 43, 1, 43, 5, 43, 700, 8, 43, 10, 43, 12, 43, 703, 9, 43, 3, 43, 705, 8, 43, 1, 43, 1, 43, 1, 43, 1, 43, 4, 43, 711, 8, 43, 11, 43, 12, 43, 712, 1, 43, 1, 43, 3, 43, 717, 8, 43, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 5, 81, 848, 8, 81, 10, 81, 12, 81, 851, 9, 81, 1, 81, 1, 81, 3, 81, 855, 8, 81, 1, 81, 4, 81, 858, 8, 81, 11, 81, 12, 81, 859, 3, 81, 862, 8, 81, 1, 82, 1, 82, 4, 82, 866, 8, 82, 11, 82, 12, 82, 867, 1, 82, 1, 82, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 3, 102, 961, 8, 102, 1, 103, 1, 103, 3, 103, 965, 8, 103, 1, 103, 5, 103, 968, 8, 103, 10, 103, 12, 103, 971, 9, 103, 1, 103, 1, 103, 3, 103, 975, 8, 103, 1, 103, 4, 103, 978, 8, 103, 11, 103, 12, 103, 979, 3, 103, 982, 8, 103, 1, 104, 1, 104, 4, 104, 986, 8, 104, 11, 104, 12, 104, 987, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 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, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 122, 4, 122, 1063, 8, 122, 11, 122, 12, 122, 1064, 1, 122, 1, 122, 3, 122, 1069, 8, 122, 1, 122, 4, 122, 1072, 8, 122, 11, 122, 12, 122, 1073, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 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, 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, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 157, 4, 157, 1228, 8, 157, 11, 157, 12, 157, 1229, 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, 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, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 173, 2, 553, 654, 0, 174, 14, 1, 16, 2, 18, 3, 20, 4, 22, 5, 24, 6, 26, 7, 28, 8, 30, 9, 32, 10, 34, 11, 36, 12, 38, 13, 40, 14, 42, 15, 44, 16, 46, 17, 48, 18, 50, 19, 52, 20, 54, 21, 56, 22, 58, 23, 60, 0, 62, 24, 64, 0, 66, 0, 68, 25, 70, 26, 72, 27, 74, 28, 76, 0, 78, 0, 80, 0, 82, 0, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 29, 98, 30, 100, 31, 102, 32, 104, 33, 106, 34, 108, 35, 110, 36, 112, 37, 114, 38, 116, 39, 118, 40, 120, 41, 122, 42, 124, 43, 126, 44, 128, 45, 130, 46, 132, 47, 134, 48, 136, 49, 138, 50, 140, 51, 142, 52, 144, 53, 146, 54, 148, 55, 150, 56, 152, 57, 154, 58, 156, 59, 158, 60, 160, 61, 162, 62, 164, 63, 166, 64, 168, 65, 170, 66, 172, 67, 174, 68, 176, 69, 178, 0, 180, 70, 182, 71, 184, 72, 186, 73, 188, 0, 190, 0, 192, 0, 194, 0, 196, 0, 198, 0, 200, 74, 202, 75, 204, 0, 206, 76, 208, 77, 210, 78, 212, 0, 214, 0, 216, 0, 218, 0, 220, 0, 222, 79, 224, 80, 226, 81, 228, 82, 230, 0, 232, 0, 234, 0, 236, 0, 238, 83, 240, 0, 242, 84, 244, 85, 246, 86, 248, 0, 250, 0, 252, 87, 254, 88, 256, 0, 258, 89, 260, 0, 262, 0, 264, 90, 266, 91, 268, 92, 270, 0, 272, 0, 274, 0, 276, 0, 278, 0, 280, 0, 282, 0, 284, 93, 286, 94, 288, 95, 290, 0, 292, 0, 294, 0, 296, 0, 298, 96, 300, 97, 302, 98, 304, 0, 306, 99, 308, 100, 310, 101, 312, 102, 314, 0, 316, 103, 318, 104, 320, 105, 322, 106, 324, 0, 326, 107, 328, 108, 330, 109, 332, 110, 334, 111, 336, 0, 338, 0, 340, 112, 342, 113, 344, 114, 346, 0, 348, 115, 350, 116, 352, 117, 354, 0, 356, 0, 358, 0, 360, 0, 14, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 10, 0, 9, 10, 13, 13, 32, 32, 44, 44, 47, 47, 61, 61, 91, 91, 93, 93, 96, 96, 124, 124, 2, 0, 42, 42, 47, 47, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 5, 0, 34, 34, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1332, 0, 14, 1, 0, 0, 0, 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, 62, 1, 0, 0, 0, 1, 64, 1, 0, 0, 0, 1, 66, 1, 0, 0, 0, 1, 68, 1, 0, 0, 0, 1, 70, 1, 0, 0, 0, 1, 72, 1, 0, 0, 0, 2, 74, 1, 0, 0, 0, 2, 96, 1, 0, 0, 0, 2, 98, 1, 0, 0, 0, 2, 100, 1, 0, 0, 0, 2, 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, 112, 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, 2, 124, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 2, 136, 1, 0, 0, 0, 2, 138, 1, 0, 0, 0, 2, 140, 1, 0, 0, 0, 2, 142, 1, 0, 0, 0, 2, 144, 1, 0, 0, 0, 2, 146, 1, 0, 0, 0, 2, 148, 1, 0, 0, 0, 2, 150, 1, 0, 0, 0, 2, 152, 1, 0, 0, 0, 2, 154, 1, 0, 0, 0, 2, 156, 1, 0, 0, 0, 2, 158, 1, 0, 0, 0, 2, 160, 1, 0, 0, 0, 2, 162, 1, 0, 0, 0, 2, 164, 1, 0, 0, 0, 2, 166, 1, 0, 0, 0, 2, 168, 1, 0, 0, 0, 2, 170, 1, 0, 0, 0, 2, 172, 1, 0, 0, 0, 2, 174, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 2, 180, 1, 0, 0, 0, 2, 182, 1, 0, 0, 0, 2, 184, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 3, 188, 1, 0, 0, 0, 3, 190, 1, 0, 0, 0, 3, 192, 1, 0, 0, 0, 3, 194, 1, 0, 0, 0, 3, 196, 1, 0, 0, 0, 3, 198, 1, 0, 0, 0, 3, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 208, 1, 0, 0, 0, 3, 210, 1, 0, 0, 0, 4, 212, 1, 0, 0, 0, 4, 214, 1, 0, 0, 0, 4, 216, 1, 0, 0, 0, 4, 222, 1, 0, 0, 0, 4, 224, 1, 0, 0, 0, 4, 226, 1, 0, 0, 0, 4, 228, 1, 0, 0, 0, 5, 230, 1, 0, 0, 0, 5, 232, 1, 0, 0, 0, 5, 234, 1, 0, 0, 0, 5, 236, 1, 0, 0, 0, 5, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 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, 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, 7, 270, 1, 0, 0, 0, 7, 272, 1, 0, 0, 0, 7, 274, 1, 0, 0, 0, 7, 276, 1, 0, 0, 0, 7, 278, 1, 0, 0, 0, 7, 280, 1, 0, 0, 0, 7, 282, 1, 0, 0, 0, 7, 284, 1, 0, 0, 0, 7, 286, 1, 0, 0, 0, 7, 288, 1, 0, 0, 0, 8, 290, 1, 0, 0, 0, 8, 292, 1, 0, 0, 0, 8, 294, 1, 0, 0, 0, 8, 296, 1, 0, 0, 0, 8, 298, 1, 0, 0, 0, 8, 300, 1, 0, 0, 0, 8, 302, 1, 0, 0, 0, 9, 304, 1, 0, 0, 0, 9, 306, 1, 0, 0, 0, 9, 308, 1, 0, 0, 0, 9, 310, 1, 0, 0, 0, 9, 312, 1, 0, 0, 0, 10, 314, 1, 0, 0, 0, 10, 316, 1, 0, 0, 0, 10, 318, 1, 0, 0, 0, 10, 320, 1, 0, 0, 0, 10, 322, 1, 0, 0, 0, 11, 324, 1, 0, 0, 0, 11, 326, 1, 0, 0, 0, 11, 328, 1, 0, 0, 0, 11, 330, 1, 0, 0, 0, 11, 332, 1, 0, 0, 0, 11, 334, 1, 0, 0, 0, 12, 336, 1, 0, 0, 0, 12, 338, 1, 0, 0, 0, 12, 340, 1, 0, 0, 0, 12, 342, 1, 0, 0, 0, 12, 344, 1, 0, 0, 0, 13, 346, 1, 0, 0, 0, 13, 348, 1, 0, 0, 0, 13, 350, 1, 0, 0, 0, 13, 352, 1, 0, 0, 0, 13, 354, 1, 0, 0, 0, 13, 356, 1, 0, 0, 0, 13, 358, 1, 0, 0, 0, 13, 360, 1, 0, 0, 0, 14, 362, 1, 0, 0, 0, 16, 372, 1, 0, 0, 0, 18, 379, 1, 0, 0, 0, 20, 388, 1, 0, 0, 0, 22, 395, 1, 0, 0, 0, 24, 405, 1, 0, 0, 0, 26, 412, 1, 0, 0, 0, 28, 419, 1, 0, 0, 0, 30, 433, 1, 0, 0, 0, 32, 440, 1, 0, 0, 0, 34, 448, 1, 0, 0, 0, 36, 455, 1, 0, 0, 0, 38, 465, 1, 0, 0, 0, 40, 477, 1, 0, 0, 0, 42, 486, 1, 0, 0, 0, 44, 492, 1, 0, 0, 0, 46, 499, 1, 0, 0, 0, 48, 506, 1, 0, 0, 0, 50, 514, 1, 0, 0, 0, 52, 523, 1, 0, 0, 0, 54, 529, 1, 0, 0, 0, 56, 546, 1, 0, 0, 0, 58, 562, 1, 0, 0, 0, 60, 571, 1, 0, 0, 0, 62, 574, 1, 0, 0, 0, 64, 578, 1, 0, 0, 0, 66, 583, 1, 0, 0, 0, 68, 588, 1, 0, 0, 0, 70, 592, 1, 0, 0, 0, 72, 596, 1, 0, 0, 0, 74, 600, 1, 0, 0, 0, 76, 604, 1, 0, 0, 0, 78, 606, 1, 0, 0, 0, 80, 608, 1, 0, 0, 0, 82, 611, 1, 0, 0, 0, 84, 613, 1, 0, 0, 0, 86, 622, 1, 0, 0, 0, 88, 624, 1, 0, 0, 0, 90, 629, 1, 0, 0, 0, 92, 631, 1, 0, 0, 0, 94, 636, 1, 0, 0, 0, 96, 667, 1, 0, 0, 0, 98, 670, 1, 0, 0, 0, 100, 716, 1, 0, 0, 0, 102, 718, 1, 0, 0, 0, 104, 721, 1, 0, 0, 0, 106, 725, 1, 0, 0, 0, 108, 729, 1, 0, 0, 0, 110, 731, 1, 0, 0, 0, 112, 734, 1, 0, 0, 0, 114, 736, 1, 0, 0, 0, 116, 741, 1, 0, 0, 0, 118, 743, 1, 0, 0, 0, 120, 749, 1, 0, 0, 0, 122, 755, 1, 0, 0, 0, 124, 760, 1, 0, 0, 0, 126, 762, 1, 0, 0, 0, 128, 765, 1, 0, 0, 0, 130, 768, 1, 0, 0, 0, 132, 773, 1, 0, 0, 0, 134, 777, 1, 0, 0, 0, 136, 782, 1, 0, 0, 0, 138, 788, 1, 0, 0, 0, 140, 791, 1, 0, 0, 0, 142, 793, 1, 0, 0, 0, 144, 799, 1, 0, 0, 0, 146, 801, 1, 0, 0, 0, 148, 806, 1, 0, 0, 0, 150, 809, 1, 0, 0, 0, 152, 812, 1, 0, 0, 0, 154, 815, 1, 0, 0, 0, 156, 817, 1, 0, 0, 0, 158, 820, 1, 0, 0, 0, 160, 822, 1, 0, 0, 0, 162, 825, 1, 0, 0, 0, 164, 827, 1, 0, 0, 0, 166, 829, 1, 0, 0, 0, 168, 831, 1, 0, 0, 0, 170, 833, 1, 0, 0, 0, 172, 835, 1, 0, 0, 0, 174, 840, 1, 0, 0, 0, 176, 861, 1, 0, 0, 0, 178, 863, 1, 0, 0, 0, 180, 871, 1, 0, 0, 0, 182, 873, 1, 0, 0, 0, 184, 877, 1, 0, 0, 0, 186, 881, 1, 0, 0, 0, 188, 885, 1, 0, 0, 0, 190, 890, 1, 0, 0, 0, 192, 894, 1, 0, 0, 0, 194, 898, 1, 0, 0, 0, 196, 902, 1, 0, 0, 0, 198, 906, 1, 0, 0, 0, 200, 910, 1, 0, 0, 0, 202, 918, 1, 0, 0, 0, 204, 927, 1, 0, 0, 0, 206, 931, 1, 0, 0, 0, 208, 935, 1, 0, 0, 0, 210, 939, 1, 0, 0, 0, 212, 943, 1, 0, 0, 0, 214, 948, 1, 0, 0, 0, 216, 952, 1, 0, 0, 0, 218, 960, 1, 0, 0, 0, 220, 981, 1, 0, 0, 0, 222, 985, 1, 0, 0, 0, 224, 989, 1, 0, 0, 0, 226, 993, 1, 0, 0, 0, 228, 997, 1, 0, 0, 0, 230, 1001, 1, 0, 0, 0, 232, 1006, 1, 0, 0, 0, 234, 1010, 1, 0, 0, 0, 236, 1014, 1, 0, 0, 0, 238, 1018, 1, 0, 0, 0, 240, 1021, 1, 0, 0, 0, 242, 1025, 1, 0, 0, 0, 244, 1029, 1, 0, 0, 0, 246, 1033, 1, 0, 0, 0, 248, 1037, 1, 0, 0, 0, 250, 1042, 1, 0, 0, 0, 252, 1047, 1, 0, 0, 0, 254, 1052, 1, 0, 0, 0, 256, 1059, 1, 0, 0, 0, 258, 1068, 1, 0, 0, 0, 260, 1075, 1, 0, 0, 0, 262, 1079, 1, 0, 0, 0, 264, 1083, 1, 0, 0, 0, 266, 1087, 1, 0, 0, 0, 268, 1091, 1, 0, 0, 0, 270, 1095, 1, 0, 0, 0, 272, 1101, 1, 0, 0, 0, 274, 1105, 1, 0, 0, 0, 276, 1109, 1, 0, 0, 0, 278, 1113, 1, 0, 0, 0, 280, 1117, 1, 0, 0, 0, 282, 1121, 1, 0, 0, 0, 284, 1125, 1, 0, 0, 0, 286, 1129, 1, 0, 0, 0, 288, 1133, 1, 0, 0, 0, 290, 1137, 1, 0, 0, 0, 292, 1142, 1, 0, 0, 0, 294, 1146, 1, 0, 0, 0, 296, 1150, 1, 0, 0, 0, 298, 1154, 1, 0, 0, 0, 300, 1158, 1, 0, 0, 0, 302, 1162, 1, 0, 0, 0, 304, 1166, 1, 0, 0, 0, 306, 1171, 1, 0, 0, 0, 308, 1176, 1, 0, 0, 0, 310, 1180, 1, 0, 0, 0, 312, 1184, 1, 0, 0, 0, 314, 1188, 1, 0, 0, 0, 316, 1193, 1, 0, 0, 0, 318, 1203, 1, 0, 0, 0, 320, 1207, 1, 0, 0, 0, 322, 1211, 1, 0, 0, 0, 324, 1215, 1, 0, 0, 0, 326, 1220, 1, 0, 0, 0, 328, 1227, 1, 0, 0, 0, 330, 1231, 1, 0, 0, 0, 332, 1235, 1, 0, 0, 0, 334, 1239, 1, 0, 0, 0, 336, 1243, 1, 0, 0, 0, 338, 1248, 1, 0, 0, 0, 340, 1254, 1, 0, 0, 0, 342, 1258, 1, 0, 0, 0, 344, 1262, 1, 0, 0, 0, 346, 1266, 1, 0, 0, 0, 348, 1272, 1, 0, 0, 0, 350, 1276, 1, 0, 0, 0, 352, 1280, 1, 0, 0, 0, 354, 1284, 1, 0, 0, 0, 356, 1290, 1, 0, 0, 0, 358, 1296, 1, 0, 0, 0, 360, 1302, 1, 0, 0, 0, 362, 363, 5, 100, 0, 0, 363, 364, 5, 105, 0, 0, 364, 365, 5, 115, 0, 0, 365, 366, 5, 115, 0, 0, 366, 367, 5, 101, 0, 0, 367, 368, 5, 99, 0, 0, 368, 369, 5, 116, 0, 0, 369, 370, 1, 0, 0, 0, 370, 371, 6, 0, 0, 0, 371, 15, 1, 0, 0, 0, 372, 373, 5, 100, 0, 0, 373, 374, 5, 114, 0, 0, 374, 375, 5, 111, 0, 0, 375, 376, 5, 112, 0, 0, 376, 377, 1, 0, 0, 0, 377, 378, 6, 1, 1, 0, 378, 17, 1, 0, 0, 0, 379, 380, 5, 101, 0, 0, 380, 381, 5, 110, 0, 0, 381, 382, 5, 114, 0, 0, 382, 383, 5, 105, 0, 0, 383, 384, 5, 99, 0, 0, 384, 385, 5, 104, 0, 0, 385, 386, 1, 0, 0, 0, 386, 387, 6, 2, 2, 0, 387, 19, 1, 0, 0, 0, 388, 389, 5, 101, 0, 0, 389, 390, 5, 118, 0, 0, 390, 391, 5, 97, 0, 0, 391, 392, 5, 108, 0, 0, 392, 393, 1, 0, 0, 0, 393, 394, 6, 3, 0, 0, 394, 21, 1, 0, 0, 0, 395, 396, 5, 101, 0, 0, 396, 397, 5, 120, 0, 0, 397, 398, 5, 112, 0, 0, 398, 399, 5, 108, 0, 0, 399, 400, 5, 97, 0, 0, 400, 401, 5, 105, 0, 0, 401, 402, 5, 110, 0, 0, 402, 403, 1, 0, 0, 0, 403, 404, 6, 4, 3, 0, 404, 23, 1, 0, 0, 0, 405, 406, 5, 102, 0, 0, 406, 407, 5, 114, 0, 0, 407, 408, 5, 111, 0, 0, 408, 409, 5, 109, 0, 0, 409, 410, 1, 0, 0, 0, 410, 411, 6, 5, 4, 0, 411, 25, 1, 0, 0, 0, 412, 413, 5, 103, 0, 0, 413, 414, 5, 114, 0, 0, 414, 415, 5, 111, 0, 0, 415, 416, 5, 107, 0, 0, 416, 417, 1, 0, 0, 0, 417, 418, 6, 6, 0, 0, 418, 27, 1, 0, 0, 0, 419, 420, 5, 105, 0, 0, 420, 421, 5, 110, 0, 0, 421, 422, 5, 108, 0, 0, 422, 423, 5, 105, 0, 0, 423, 424, 5, 110, 0, 0, 424, 425, 5, 101, 0, 0, 425, 426, 5, 115, 0, 0, 426, 427, 5, 116, 0, 0, 427, 428, 5, 97, 0, 0, 428, 429, 5, 116, 0, 0, 429, 430, 5, 115, 0, 0, 430, 431, 1, 0, 0, 0, 431, 432, 6, 7, 0, 0, 432, 29, 1, 0, 0, 0, 433, 434, 5, 107, 0, 0, 434, 435, 5, 101, 0, 0, 435, 436, 5, 101, 0, 0, 436, 437, 5, 112, 0, 0, 437, 438, 1, 0, 0, 0, 438, 439, 6, 8, 1, 0, 439, 31, 1, 0, 0, 0, 440, 441, 5, 108, 0, 0, 441, 442, 5, 105, 0, 0, 442, 443, 5, 109, 0, 0, 443, 444, 5, 105, 0, 0, 444, 445, 5, 116, 0, 0, 445, 446, 1, 0, 0, 0, 446, 447, 6, 9, 0, 0, 447, 33, 1, 0, 0, 0, 448, 449, 5, 109, 0, 0, 449, 450, 5, 101, 0, 0, 450, 451, 5, 116, 0, 0, 451, 452, 5, 97, 0, 0, 452, 453, 1, 0, 0, 0, 453, 454, 6, 10, 5, 0, 454, 35, 1, 0, 0, 0, 455, 456, 5, 109, 0, 0, 456, 457, 5, 101, 0, 0, 457, 458, 5, 116, 0, 0, 458, 459, 5, 114, 0, 0, 459, 460, 5, 105, 0, 0, 460, 461, 5, 99, 0, 0, 461, 462, 5, 115, 0, 0, 462, 463, 1, 0, 0, 0, 463, 464, 6, 11, 6, 0, 464, 37, 1, 0, 0, 0, 465, 466, 5, 109, 0, 0, 466, 467, 5, 118, 0, 0, 467, 468, 5, 95, 0, 0, 468, 469, 5, 101, 0, 0, 469, 470, 5, 120, 0, 0, 470, 471, 5, 112, 0, 0, 471, 472, 5, 97, 0, 0, 472, 473, 5, 110, 0, 0, 473, 474, 5, 100, 0, 0, 474, 475, 1, 0, 0, 0, 475, 476, 6, 12, 7, 0, 476, 39, 1, 0, 0, 0, 477, 478, 5, 114, 0, 0, 478, 479, 5, 101, 0, 0, 479, 480, 5, 110, 0, 0, 480, 481, 5, 97, 0, 0, 481, 482, 5, 109, 0, 0, 482, 483, 5, 101, 0, 0, 483, 484, 1, 0, 0, 0, 484, 485, 6, 13, 8, 0, 485, 41, 1, 0, 0, 0, 486, 487, 5, 114, 0, 0, 487, 488, 5, 111, 0, 0, 488, 489, 5, 119, 0, 0, 489, 490, 1, 0, 0, 0, 490, 491, 6, 14, 0, 0, 491, 43, 1, 0, 0, 0, 492, 493, 5, 115, 0, 0, 493, 494, 5, 104, 0, 0, 494, 495, 5, 111, 0, 0, 495, 496, 5, 119, 0, 0, 496, 497, 1, 0, 0, 0, 497, 498, 6, 15, 9, 0, 498, 45, 1, 0, 0, 0, 499, 500, 5, 115, 0, 0, 500, 501, 5, 111, 0, 0, 501, 502, 5, 114, 0, 0, 502, 503, 5, 116, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 6, 16, 0, 0, 505, 47, 1, 0, 0, 0, 506, 507, 5, 115, 0, 0, 507, 508, 5, 116, 0, 0, 508, 509, 5, 97, 0, 0, 509, 510, 5, 116, 0, 0, 510, 511, 5, 115, 0, 0, 511, 512, 1, 0, 0, 0, 512, 513, 6, 17, 0, 0, 513, 49, 1, 0, 0, 0, 514, 515, 5, 119, 0, 0, 515, 516, 5, 104, 0, 0, 516, 517, 5, 101, 0, 0, 517, 518, 5, 114, 0, 0, 518, 519, 5, 101, 0, 0, 519, 520, 1, 0, 0, 0, 520, 521, 6, 18, 0, 0, 521, 51, 1, 0, 0, 0, 522, 524, 8, 0, 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, 19, 0, 0, 528, 53, 1, 0, 0, 0, 529, 530, 5, 47, 0, 0, 530, 531, 5, 47, 0, 0, 531, 535, 1, 0, 0, 0, 532, 534, 8, 1, 0, 0, 533, 532, 1, 0, 0, 0, 534, 537, 1, 0, 0, 0, 535, 533, 1, 0, 0, 0, 535, 536, 1, 0, 0, 0, 536, 539, 1, 0, 0, 0, 537, 535, 1, 0, 0, 0, 538, 540, 5, 13, 0, 0, 539, 538, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 542, 1, 0, 0, 0, 541, 543, 5, 10, 0, 0, 542, 541, 1, 0, 0, 0, 542, 543, 1, 0, 0, 0, 543, 544, 1, 0, 0, 0, 544, 545, 6, 20, 10, 0, 545, 55, 1, 0, 0, 0, 546, 547, 5, 47, 0, 0, 547, 548, 5, 42, 0, 0, 548, 553, 1, 0, 0, 0, 549, 552, 3, 56, 21, 0, 550, 552, 9, 0, 0, 0, 551, 549, 1, 0, 0, 0, 551, 550, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 1, 0, 0, 0, 553, 551, 1, 0, 0, 0, 554, 556, 1, 0, 0, 0, 555, 553, 1, 0, 0, 0, 556, 557, 5, 42, 0, 0, 557, 558, 5, 47, 0, 0, 558, 559, 1, 0, 0, 0, 559, 560, 6, 21, 10, 0, 560, 57, 1, 0, 0, 0, 561, 563, 7, 2, 0, 0, 562, 561, 1, 0, 0, 0, 563, 564, 1, 0, 0, 0, 564, 562, 1, 0, 0, 0, 564, 565, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 567, 6, 22, 10, 0, 567, 59, 1, 0, 0, 0, 568, 572, 8, 3, 0, 0, 569, 570, 5, 47, 0, 0, 570, 572, 8, 4, 0, 0, 571, 568, 1, 0, 0, 0, 571, 569, 1, 0, 0, 0, 572, 61, 1, 0, 0, 0, 573, 575, 3, 60, 23, 0, 574, 573, 1, 0, 0, 0, 575, 576, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 576, 577, 1, 0, 0, 0, 577, 63, 1, 0, 0, 0, 578, 579, 3, 172, 79, 0, 579, 580, 1, 0, 0, 0, 580, 581, 6, 25, 11, 0, 581, 582, 6, 25, 12, 0, 582, 65, 1, 0, 0, 0, 583, 584, 3, 74, 30, 0, 584, 585, 1, 0, 0, 0, 585, 586, 6, 26, 13, 0, 586, 587, 6, 26, 14, 0, 587, 67, 1, 0, 0, 0, 588, 589, 3, 58, 22, 0, 589, 590, 1, 0, 0, 0, 590, 591, 6, 27, 10, 0, 591, 69, 1, 0, 0, 0, 592, 593, 3, 54, 20, 0, 593, 594, 1, 0, 0, 0, 594, 595, 6, 28, 10, 0, 595, 71, 1, 0, 0, 0, 596, 597, 3, 56, 21, 0, 597, 598, 1, 0, 0, 0, 598, 599, 6, 29, 10, 0, 599, 73, 1, 0, 0, 0, 600, 601, 5, 124, 0, 0, 601, 602, 1, 0, 0, 0, 602, 603, 6, 30, 14, 0, 603, 75, 1, 0, 0, 0, 604, 605, 7, 5, 0, 0, 605, 77, 1, 0, 0, 0, 606, 607, 7, 6, 0, 0, 607, 79, 1, 0, 0, 0, 608, 609, 5, 92, 0, 0, 609, 610, 7, 7, 0, 0, 610, 81, 1, 0, 0, 0, 611, 612, 8, 8, 0, 0, 612, 83, 1, 0, 0, 0, 613, 615, 7, 9, 0, 0, 614, 616, 7, 10, 0, 0, 615, 614, 1, 0, 0, 0, 615, 616, 1, 0, 0, 0, 616, 618, 1, 0, 0, 0, 617, 619, 3, 76, 31, 0, 618, 617, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 618, 1, 0, 0, 0, 620, 621, 1, 0, 0, 0, 621, 85, 1, 0, 0, 0, 622, 623, 5, 64, 0, 0, 623, 87, 1, 0, 0, 0, 624, 625, 5, 96, 0, 0, 625, 89, 1, 0, 0, 0, 626, 630, 8, 11, 0, 0, 627, 628, 5, 96, 0, 0, 628, 630, 5, 96, 0, 0, 629, 626, 1, 0, 0, 0, 629, 627, 1, 0, 0, 0, 630, 91, 1, 0, 0, 0, 631, 632, 5, 95, 0, 0, 632, 93, 1, 0, 0, 0, 633, 637, 3, 78, 32, 0, 634, 637, 3, 76, 31, 0, 635, 637, 3, 92, 39, 0, 636, 633, 1, 0, 0, 0, 636, 634, 1, 0, 0, 0, 636, 635, 1, 0, 0, 0, 637, 95, 1, 0, 0, 0, 638, 643, 5, 34, 0, 0, 639, 642, 3, 80, 33, 0, 640, 642, 3, 82, 34, 0, 641, 639, 1, 0, 0, 0, 641, 640, 1, 0, 0, 0, 642, 645, 1, 0, 0, 0, 643, 641, 1, 0, 0, 0, 643, 644, 1, 0, 0, 0, 644, 646, 1, 0, 0, 0, 645, 643, 1, 0, 0, 0, 646, 668, 5, 34, 0, 0, 647, 648, 5, 34, 0, 0, 648, 649, 5, 34, 0, 0, 649, 650, 5, 34, 0, 0, 650, 654, 1, 0, 0, 0, 651, 653, 8, 1, 0, 0, 652, 651, 1, 0, 0, 0, 653, 656, 1, 0, 0, 0, 654, 655, 1, 0, 0, 0, 654, 652, 1, 0, 0, 0, 655, 657, 1, 0, 0, 0, 656, 654, 1, 0, 0, 0, 657, 658, 5, 34, 0, 0, 658, 659, 5, 34, 0, 0, 659, 660, 5, 34, 0, 0, 660, 662, 1, 0, 0, 0, 661, 663, 5, 34, 0, 0, 662, 661, 1, 0, 0, 0, 662, 663, 1, 0, 0, 0, 663, 665, 1, 0, 0, 0, 664, 666, 5, 34, 0, 0, 665, 664, 1, 0, 0, 0, 665, 666, 1, 0, 0, 0, 666, 668, 1, 0, 0, 0, 667, 638, 1, 0, 0, 0, 667, 647, 1, 0, 0, 0, 668, 97, 1, 0, 0, 0, 669, 671, 3, 76, 31, 0, 670, 669, 1, 0, 0, 0, 671, 672, 1, 0, 0, 0, 672, 670, 1, 0, 0, 0, 672, 673, 1, 0, 0, 0, 673, 99, 1, 0, 0, 0, 674, 676, 3, 76, 31, 0, 675, 674, 1, 0, 0, 0, 676, 677, 1, 0, 0, 0, 677, 675, 1, 0, 0, 0, 677, 678, 1, 0, 0, 0, 678, 679, 1, 0, 0, 0, 679, 683, 3, 116, 51, 0, 680, 682, 3, 76, 31, 0, 681, 680, 1, 0, 0, 0, 682, 685, 1, 0, 0, 0, 683, 681, 1, 0, 0, 0, 683, 684, 1, 0, 0, 0, 684, 717, 1, 0, 0, 0, 685, 683, 1, 0, 0, 0, 686, 688, 3, 116, 51, 0, 687, 689, 3, 76, 31, 0, 688, 687, 1, 0, 0, 0, 689, 690, 1, 0, 0, 0, 690, 688, 1, 0, 0, 0, 690, 691, 1, 0, 0, 0, 691, 717, 1, 0, 0, 0, 692, 694, 3, 76, 31, 0, 693, 692, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 704, 1, 0, 0, 0, 697, 701, 3, 116, 51, 0, 698, 700, 3, 76, 31, 0, 699, 698, 1, 0, 0, 0, 700, 703, 1, 0, 0, 0, 701, 699, 1, 0, 0, 0, 701, 702, 1, 0, 0, 0, 702, 705, 1, 0, 0, 0, 703, 701, 1, 0, 0, 0, 704, 697, 1, 0, 0, 0, 704, 705, 1, 0, 0, 0, 705, 706, 1, 0, 0, 0, 706, 707, 3, 84, 35, 0, 707, 717, 1, 0, 0, 0, 708, 710, 3, 116, 51, 0, 709, 711, 3, 76, 31, 0, 710, 709, 1, 0, 0, 0, 711, 712, 1, 0, 0, 0, 712, 710, 1, 0, 0, 0, 712, 713, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 715, 3, 84, 35, 0, 715, 717, 1, 0, 0, 0, 716, 675, 1, 0, 0, 0, 716, 686, 1, 0, 0, 0, 716, 693, 1, 0, 0, 0, 716, 708, 1, 0, 0, 0, 717, 101, 1, 0, 0, 0, 718, 719, 5, 98, 0, 0, 719, 720, 5, 121, 0, 0, 720, 103, 1, 0, 0, 0, 721, 722, 5, 97, 0, 0, 722, 723, 5, 110, 0, 0, 723, 724, 5, 100, 0, 0, 724, 105, 1, 0, 0, 0, 725, 726, 5, 97, 0, 0, 726, 727, 5, 115, 0, 0, 727, 728, 5, 99, 0, 0, 728, 107, 1, 0, 0, 0, 729, 730, 5, 61, 0, 0, 730, 109, 1, 0, 0, 0, 731, 732, 5, 58, 0, 0, 732, 733, 5, 58, 0, 0, 733, 111, 1, 0, 0, 0, 734, 735, 5, 44, 0, 0, 735, 113, 1, 0, 0, 0, 736, 737, 5, 100, 0, 0, 737, 738, 5, 101, 0, 0, 738, 739, 5, 115, 0, 0, 739, 740, 5, 99, 0, 0, 740, 115, 1, 0, 0, 0, 741, 742, 5, 46, 0, 0, 742, 117, 1, 0, 0, 0, 743, 744, 5, 102, 0, 0, 744, 745, 5, 97, 0, 0, 745, 746, 5, 108, 0, 0, 746, 747, 5, 115, 0, 0, 747, 748, 5, 101, 0, 0, 748, 119, 1, 0, 0, 0, 749, 750, 5, 102, 0, 0, 750, 751, 5, 105, 0, 0, 751, 752, 5, 114, 0, 0, 752, 753, 5, 115, 0, 0, 753, 754, 5, 116, 0, 0, 754, 121, 1, 0, 0, 0, 755, 756, 5, 108, 0, 0, 756, 757, 5, 97, 0, 0, 757, 758, 5, 115, 0, 0, 758, 759, 5, 116, 0, 0, 759, 123, 1, 0, 0, 0, 760, 761, 5, 40, 0, 0, 761, 125, 1, 0, 0, 0, 762, 763, 5, 105, 0, 0, 763, 764, 5, 110, 0, 0, 764, 127, 1, 0, 0, 0, 765, 766, 5, 105, 0, 0, 766, 767, 5, 115, 0, 0, 767, 129, 1, 0, 0, 0, 768, 769, 5, 108, 0, 0, 769, 770, 5, 105, 0, 0, 770, 771, 5, 107, 0, 0, 771, 772, 5, 101, 0, 0, 772, 131, 1, 0, 0, 0, 773, 774, 5, 110, 0, 0, 774, 775, 5, 111, 0, 0, 775, 776, 5, 116, 0, 0, 776, 133, 1, 0, 0, 0, 777, 778, 5, 110, 0, 0, 778, 779, 5, 117, 0, 0, 779, 780, 5, 108, 0, 0, 780, 781, 5, 108, 0, 0, 781, 135, 1, 0, 0, 0, 782, 783, 5, 110, 0, 0, 783, 784, 5, 117, 0, 0, 784, 785, 5, 108, 0, 0, 785, 786, 5, 108, 0, 0, 786, 787, 5, 115, 0, 0, 787, 137, 1, 0, 0, 0, 788, 789, 5, 111, 0, 0, 789, 790, 5, 114, 0, 0, 790, 139, 1, 0, 0, 0, 791, 792, 5, 63, 0, 0, 792, 141, 1, 0, 0, 0, 793, 794, 5, 114, 0, 0, 794, 795, 5, 108, 0, 0, 795, 796, 5, 105, 0, 0, 796, 797, 5, 107, 0, 0, 797, 798, 5, 101, 0, 0, 798, 143, 1, 0, 0, 0, 799, 800, 5, 41, 0, 0, 800, 145, 1, 0, 0, 0, 801, 802, 5, 116, 0, 0, 802, 803, 5, 114, 0, 0, 803, 804, 5, 117, 0, 0, 804, 805, 5, 101, 0, 0, 805, 147, 1, 0, 0, 0, 806, 807, 5, 61, 0, 0, 807, 808, 5, 61, 0, 0, 808, 149, 1, 0, 0, 0, 809, 810, 5, 61, 0, 0, 810, 811, 5, 126, 0, 0, 811, 151, 1, 0, 0, 0, 812, 813, 5, 33, 0, 0, 813, 814, 5, 61, 0, 0, 814, 153, 1, 0, 0, 0, 815, 816, 5, 60, 0, 0, 816, 155, 1, 0, 0, 0, 817, 818, 5, 60, 0, 0, 818, 819, 5, 61, 0, 0, 819, 157, 1, 0, 0, 0, 820, 821, 5, 62, 0, 0, 821, 159, 1, 0, 0, 0, 822, 823, 5, 62, 0, 0, 823, 824, 5, 61, 0, 0, 824, 161, 1, 0, 0, 0, 825, 826, 5, 43, 0, 0, 826, 163, 1, 0, 0, 0, 827, 828, 5, 45, 0, 0, 828, 165, 1, 0, 0, 0, 829, 830, 5, 42, 0, 0, 830, 167, 1, 0, 0, 0, 831, 832, 5, 47, 0, 0, 832, 169, 1, 0, 0, 0, 833, 834, 5, 37, 0, 0, 834, 171, 1, 0, 0, 0, 835, 836, 5, 91, 0, 0, 836, 837, 1, 0, 0, 0, 837, 838, 6, 79, 0, 0, 838, 839, 6, 79, 0, 0, 839, 173, 1, 0, 0, 0, 840, 841, 5, 93, 0, 0, 841, 842, 1, 0, 0, 0, 842, 843, 6, 80, 14, 0, 843, 844, 6, 80, 14, 0, 844, 175, 1, 0, 0, 0, 845, 849, 3, 78, 32, 0, 846, 848, 3, 94, 40, 0, 847, 846, 1, 0, 0, 0, 848, 851, 1, 0, 0, 0, 849, 847, 1, 0, 0, 0, 849, 850, 1, 0, 0, 0, 850, 862, 1, 0, 0, 0, 851, 849, 1, 0, 0, 0, 852, 855, 3, 92, 39, 0, 853, 855, 3, 86, 36, 0, 854, 852, 1, 0, 0, 0, 854, 853, 1, 0, 0, 0, 855, 857, 1, 0, 0, 0, 856, 858, 3, 94, 40, 0, 857, 856, 1, 0, 0, 0, 858, 859, 1, 0, 0, 0, 859, 857, 1, 0, 0, 0, 859, 860, 1, 0, 0, 0, 860, 862, 1, 0, 0, 0, 861, 845, 1, 0, 0, 0, 861, 854, 1, 0, 0, 0, 862, 177, 1, 0, 0, 0, 863, 865, 3, 88, 37, 0, 864, 866, 3, 90, 38, 0, 865, 864, 1, 0, 0, 0, 866, 867, 1, 0, 0, 0, 867, 865, 1, 0, 0, 0, 867, 868, 1, 0, 0, 0, 868, 869, 1, 0, 0, 0, 869, 870, 3, 88, 37, 0, 870, 179, 1, 0, 0, 0, 871, 872, 3, 178, 82, 0, 872, 181, 1, 0, 0, 0, 873, 874, 3, 54, 20, 0, 874, 875, 1, 0, 0, 0, 875, 876, 6, 84, 10, 0, 876, 183, 1, 0, 0, 0, 877, 878, 3, 56, 21, 0, 878, 879, 1, 0, 0, 0, 879, 880, 6, 85, 10, 0, 880, 185, 1, 0, 0, 0, 881, 882, 3, 58, 22, 0, 882, 883, 1, 0, 0, 0, 883, 884, 6, 86, 10, 0, 884, 187, 1, 0, 0, 0, 885, 886, 3, 74, 30, 0, 886, 887, 1, 0, 0, 0, 887, 888, 6, 87, 13, 0, 888, 889, 6, 87, 14, 0, 889, 189, 1, 0, 0, 0, 890, 891, 3, 172, 79, 0, 891, 892, 1, 0, 0, 0, 892, 893, 6, 88, 11, 0, 893, 191, 1, 0, 0, 0, 894, 895, 3, 174, 80, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 89, 15, 0, 897, 193, 1, 0, 0, 0, 898, 899, 3, 112, 49, 0, 899, 900, 1, 0, 0, 0, 900, 901, 6, 90, 16, 0, 901, 195, 1, 0, 0, 0, 902, 903, 3, 108, 47, 0, 903, 904, 1, 0, 0, 0, 904, 905, 6, 91, 17, 0, 905, 197, 1, 0, 0, 0, 906, 907, 3, 96, 41, 0, 907, 908, 1, 0, 0, 0, 908, 909, 6, 92, 18, 0, 909, 199, 1, 0, 0, 0, 910, 911, 5, 111, 0, 0, 911, 912, 5, 112, 0, 0, 912, 913, 5, 116, 0, 0, 913, 914, 5, 105, 0, 0, 914, 915, 5, 111, 0, 0, 915, 916, 5, 110, 0, 0, 916, 917, 5, 115, 0, 0, 917, 201, 1, 0, 0, 0, 918, 919, 5, 109, 0, 0, 919, 920, 5, 101, 0, 0, 920, 921, 5, 116, 0, 0, 921, 922, 5, 97, 0, 0, 922, 923, 5, 100, 0, 0, 923, 924, 5, 97, 0, 0, 924, 925, 5, 116, 0, 0, 925, 926, 5, 97, 0, 0, 926, 203, 1, 0, 0, 0, 927, 928, 3, 62, 24, 0, 928, 929, 1, 0, 0, 0, 929, 930, 6, 95, 19, 0, 930, 205, 1, 0, 0, 0, 931, 932, 3, 54, 20, 0, 932, 933, 1, 0, 0, 0, 933, 934, 6, 96, 10, 0, 934, 207, 1, 0, 0, 0, 935, 936, 3, 56, 21, 0, 936, 937, 1, 0, 0, 0, 937, 938, 6, 97, 10, 0, 938, 209, 1, 0, 0, 0, 939, 940, 3, 58, 22, 0, 940, 941, 1, 0, 0, 0, 941, 942, 6, 98, 10, 0, 942, 211, 1, 0, 0, 0, 943, 944, 3, 74, 30, 0, 944, 945, 1, 0, 0, 0, 945, 946, 6, 99, 13, 0, 946, 947, 6, 99, 14, 0, 947, 213, 1, 0, 0, 0, 948, 949, 3, 116, 51, 0, 949, 950, 1, 0, 0, 0, 950, 951, 6, 100, 20, 0, 951, 215, 1, 0, 0, 0, 952, 953, 3, 112, 49, 0, 953, 954, 1, 0, 0, 0, 954, 955, 6, 101, 16, 0, 955, 217, 1, 0, 0, 0, 956, 961, 3, 78, 32, 0, 957, 961, 3, 76, 31, 0, 958, 961, 3, 92, 39, 0, 959, 961, 3, 166, 76, 0, 960, 956, 1, 0, 0, 0, 960, 957, 1, 0, 0, 0, 960, 958, 1, 0, 0, 0, 960, 959, 1, 0, 0, 0, 961, 219, 1, 0, 0, 0, 962, 965, 3, 78, 32, 0, 963, 965, 3, 166, 76, 0, 964, 962, 1, 0, 0, 0, 964, 963, 1, 0, 0, 0, 965, 969, 1, 0, 0, 0, 966, 968, 3, 218, 102, 0, 967, 966, 1, 0, 0, 0, 968, 971, 1, 0, 0, 0, 969, 967, 1, 0, 0, 0, 969, 970, 1, 0, 0, 0, 970, 982, 1, 0, 0, 0, 971, 969, 1, 0, 0, 0, 972, 975, 3, 92, 39, 0, 973, 975, 3, 86, 36, 0, 974, 972, 1, 0, 0, 0, 974, 973, 1, 0, 0, 0, 975, 977, 1, 0, 0, 0, 976, 978, 3, 218, 102, 0, 977, 976, 1, 0, 0, 0, 978, 979, 1, 0, 0, 0, 979, 977, 1, 0, 0, 0, 979, 980, 1, 0, 0, 0, 980, 982, 1, 0, 0, 0, 981, 964, 1, 0, 0, 0, 981, 974, 1, 0, 0, 0, 982, 221, 1, 0, 0, 0, 983, 986, 3, 220, 103, 0, 984, 986, 3, 178, 82, 0, 985, 983, 1, 0, 0, 0, 985, 984, 1, 0, 0, 0, 986, 987, 1, 0, 0, 0, 987, 985, 1, 0, 0, 0, 987, 988, 1, 0, 0, 0, 988, 223, 1, 0, 0, 0, 989, 990, 3, 54, 20, 0, 990, 991, 1, 0, 0, 0, 991, 992, 6, 105, 10, 0, 992, 225, 1, 0, 0, 0, 993, 994, 3, 56, 21, 0, 994, 995, 1, 0, 0, 0, 995, 996, 6, 106, 10, 0, 996, 227, 1, 0, 0, 0, 997, 998, 3, 58, 22, 0, 998, 999, 1, 0, 0, 0, 999, 1000, 6, 107, 10, 0, 1000, 229, 1, 0, 0, 0, 1001, 1002, 3, 74, 30, 0, 1002, 1003, 1, 0, 0, 0, 1003, 1004, 6, 108, 13, 0, 1004, 1005, 6, 108, 14, 0, 1005, 231, 1, 0, 0, 0, 1006, 1007, 3, 108, 47, 0, 1007, 1008, 1, 0, 0, 0, 1008, 1009, 6, 109, 17, 0, 1009, 233, 1, 0, 0, 0, 1010, 1011, 3, 112, 49, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1013, 6, 110, 16, 0, 1013, 235, 1, 0, 0, 0, 1014, 1015, 3, 116, 51, 0, 1015, 1016, 1, 0, 0, 0, 1016, 1017, 6, 111, 20, 0, 1017, 237, 1, 0, 0, 0, 1018, 1019, 5, 97, 0, 0, 1019, 1020, 5, 115, 0, 0, 1020, 239, 1, 0, 0, 0, 1021, 1022, 3, 222, 104, 0, 1022, 1023, 1, 0, 0, 0, 1023, 1024, 6, 113, 21, 0, 1024, 241, 1, 0, 0, 0, 1025, 1026, 3, 54, 20, 0, 1026, 1027, 1, 0, 0, 0, 1027, 1028, 6, 114, 10, 0, 1028, 243, 1, 0, 0, 0, 1029, 1030, 3, 56, 21, 0, 1030, 1031, 1, 0, 0, 0, 1031, 1032, 6, 115, 10, 0, 1032, 245, 1, 0, 0, 0, 1033, 1034, 3, 58, 22, 0, 1034, 1035, 1, 0, 0, 0, 1035, 1036, 6, 116, 10, 0, 1036, 247, 1, 0, 0, 0, 1037, 1038, 3, 74, 30, 0, 1038, 1039, 1, 0, 0, 0, 1039, 1040, 6, 117, 13, 0, 1040, 1041, 6, 117, 14, 0, 1041, 249, 1, 0, 0, 0, 1042, 1043, 3, 172, 79, 0, 1043, 1044, 1, 0, 0, 0, 1044, 1045, 6, 118, 11, 0, 1045, 1046, 6, 118, 22, 0, 1046, 251, 1, 0, 0, 0, 1047, 1048, 5, 111, 0, 0, 1048, 1049, 5, 110, 0, 0, 1049, 1050, 1, 0, 0, 0, 1050, 1051, 6, 119, 23, 0, 1051, 253, 1, 0, 0, 0, 1052, 1053, 5, 119, 0, 0, 1053, 1054, 5, 105, 0, 0, 1054, 1055, 5, 116, 0, 0, 1055, 1056, 5, 104, 0, 0, 1056, 1057, 1, 0, 0, 0, 1057, 1058, 6, 120, 23, 0, 1058, 255, 1, 0, 0, 0, 1059, 1060, 8, 12, 0, 0, 1060, 257, 1, 0, 0, 0, 1061, 1063, 3, 256, 121, 0, 1062, 1061, 1, 0, 0, 0, 1063, 1064, 1, 0, 0, 0, 1064, 1062, 1, 0, 0, 0, 1064, 1065, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 1067, 3, 326, 156, 0, 1067, 1069, 1, 0, 0, 0, 1068, 1062, 1, 0, 0, 0, 1068, 1069, 1, 0, 0, 0, 1069, 1071, 1, 0, 0, 0, 1070, 1072, 3, 256, 121, 0, 1071, 1070, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1071, 1, 0, 0, 0, 1073, 1074, 1, 0, 0, 0, 1074, 259, 1, 0, 0, 0, 1075, 1076, 3, 180, 83, 0, 1076, 1077, 1, 0, 0, 0, 1077, 1078, 6, 123, 24, 0, 1078, 261, 1, 0, 0, 0, 1079, 1080, 3, 258, 122, 0, 1080, 1081, 1, 0, 0, 0, 1081, 1082, 6, 124, 25, 0, 1082, 263, 1, 0, 0, 0, 1083, 1084, 3, 54, 20, 0, 1084, 1085, 1, 0, 0, 0, 1085, 1086, 6, 125, 10, 0, 1086, 265, 1, 0, 0, 0, 1087, 1088, 3, 56, 21, 0, 1088, 1089, 1, 0, 0, 0, 1089, 1090, 6, 126, 10, 0, 1090, 267, 1, 0, 0, 0, 1091, 1092, 3, 58, 22, 0, 1092, 1093, 1, 0, 0, 0, 1093, 1094, 6, 127, 10, 0, 1094, 269, 1, 0, 0, 0, 1095, 1096, 3, 74, 30, 0, 1096, 1097, 1, 0, 0, 0, 1097, 1098, 6, 128, 13, 0, 1098, 1099, 6, 128, 14, 0, 1099, 1100, 6, 128, 14, 0, 1100, 271, 1, 0, 0, 0, 1101, 1102, 3, 108, 47, 0, 1102, 1103, 1, 0, 0, 0, 1103, 1104, 6, 129, 17, 0, 1104, 273, 1, 0, 0, 0, 1105, 1106, 3, 112, 49, 0, 1106, 1107, 1, 0, 0, 0, 1107, 1108, 6, 130, 16, 0, 1108, 275, 1, 0, 0, 0, 1109, 1110, 3, 116, 51, 0, 1110, 1111, 1, 0, 0, 0, 1111, 1112, 6, 131, 20, 0, 1112, 277, 1, 0, 0, 0, 1113, 1114, 3, 254, 120, 0, 1114, 1115, 1, 0, 0, 0, 1115, 1116, 6, 132, 26, 0, 1116, 279, 1, 0, 0, 0, 1117, 1118, 3, 222, 104, 0, 1118, 1119, 1, 0, 0, 0, 1119, 1120, 6, 133, 21, 0, 1120, 281, 1, 0, 0, 0, 1121, 1122, 3, 180, 83, 0, 1122, 1123, 1, 0, 0, 0, 1123, 1124, 6, 134, 24, 0, 1124, 283, 1, 0, 0, 0, 1125, 1126, 3, 54, 20, 0, 1126, 1127, 1, 0, 0, 0, 1127, 1128, 6, 135, 10, 0, 1128, 285, 1, 0, 0, 0, 1129, 1130, 3, 56, 21, 0, 1130, 1131, 1, 0, 0, 0, 1131, 1132, 6, 136, 10, 0, 1132, 287, 1, 0, 0, 0, 1133, 1134, 3, 58, 22, 0, 1134, 1135, 1, 0, 0, 0, 1135, 1136, 6, 137, 10, 0, 1136, 289, 1, 0, 0, 0, 1137, 1138, 3, 74, 30, 0, 1138, 1139, 1, 0, 0, 0, 1139, 1140, 6, 138, 13, 0, 1140, 1141, 6, 138, 14, 0, 1141, 291, 1, 0, 0, 0, 1142, 1143, 3, 116, 51, 0, 1143, 1144, 1, 0, 0, 0, 1144, 1145, 6, 139, 20, 0, 1145, 293, 1, 0, 0, 0, 1146, 1147, 3, 180, 83, 0, 1147, 1148, 1, 0, 0, 0, 1148, 1149, 6, 140, 24, 0, 1149, 295, 1, 0, 0, 0, 1150, 1151, 3, 176, 81, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 6, 141, 27, 0, 1153, 297, 1, 0, 0, 0, 1154, 1155, 3, 54, 20, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1157, 6, 142, 10, 0, 1157, 299, 1, 0, 0, 0, 1158, 1159, 3, 56, 21, 0, 1159, 1160, 1, 0, 0, 0, 1160, 1161, 6, 143, 10, 0, 1161, 301, 1, 0, 0, 0, 1162, 1163, 3, 58, 22, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 6, 144, 10, 0, 1165, 303, 1, 0, 0, 0, 1166, 1167, 3, 74, 30, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 6, 145, 13, 0, 1169, 1170, 6, 145, 14, 0, 1170, 305, 1, 0, 0, 0, 1171, 1172, 5, 105, 0, 0, 1172, 1173, 5, 110, 0, 0, 1173, 1174, 5, 102, 0, 0, 1174, 1175, 5, 111, 0, 0, 1175, 307, 1, 0, 0, 0, 1176, 1177, 3, 54, 20, 0, 1177, 1178, 1, 0, 0, 0, 1178, 1179, 6, 147, 10, 0, 1179, 309, 1, 0, 0, 0, 1180, 1181, 3, 56, 21, 0, 1181, 1182, 1, 0, 0, 0, 1182, 1183, 6, 148, 10, 0, 1183, 311, 1, 0, 0, 0, 1184, 1185, 3, 58, 22, 0, 1185, 1186, 1, 0, 0, 0, 1186, 1187, 6, 149, 10, 0, 1187, 313, 1, 0, 0, 0, 1188, 1189, 3, 74, 30, 0, 1189, 1190, 1, 0, 0, 0, 1190, 1191, 6, 150, 13, 0, 1191, 1192, 6, 150, 14, 0, 1192, 315, 1, 0, 0, 0, 1193, 1194, 5, 102, 0, 0, 1194, 1195, 5, 117, 0, 0, 1195, 1196, 5, 110, 0, 0, 1196, 1197, 5, 99, 0, 0, 1197, 1198, 5, 116, 0, 0, 1198, 1199, 5, 105, 0, 0, 1199, 1200, 5, 111, 0, 0, 1200, 1201, 5, 110, 0, 0, 1201, 1202, 5, 115, 0, 0, 1202, 317, 1, 0, 0, 0, 1203, 1204, 3, 54, 20, 0, 1204, 1205, 1, 0, 0, 0, 1205, 1206, 6, 152, 10, 0, 1206, 319, 1, 0, 0, 0, 1207, 1208, 3, 56, 21, 0, 1208, 1209, 1, 0, 0, 0, 1209, 1210, 6, 153, 10, 0, 1210, 321, 1, 0, 0, 0, 1211, 1212, 3, 58, 22, 0, 1212, 1213, 1, 0, 0, 0, 1213, 1214, 6, 154, 10, 0, 1214, 323, 1, 0, 0, 0, 1215, 1216, 3, 174, 80, 0, 1216, 1217, 1, 0, 0, 0, 1217, 1218, 6, 155, 15, 0, 1218, 1219, 6, 155, 14, 0, 1219, 325, 1, 0, 0, 0, 1220, 1221, 5, 58, 0, 0, 1221, 327, 1, 0, 0, 0, 1222, 1228, 3, 86, 36, 0, 1223, 1228, 3, 76, 31, 0, 1224, 1228, 3, 116, 51, 0, 1225, 1228, 3, 78, 32, 0, 1226, 1228, 3, 92, 39, 0, 1227, 1222, 1, 0, 0, 0, 1227, 1223, 1, 0, 0, 0, 1227, 1224, 1, 0, 0, 0, 1227, 1225, 1, 0, 0, 0, 1227, 1226, 1, 0, 0, 0, 1228, 1229, 1, 0, 0, 0, 1229, 1227, 1, 0, 0, 0, 1229, 1230, 1, 0, 0, 0, 1230, 329, 1, 0, 0, 0, 1231, 1232, 3, 54, 20, 0, 1232, 1233, 1, 0, 0, 0, 1233, 1234, 6, 158, 10, 0, 1234, 331, 1, 0, 0, 0, 1235, 1236, 3, 56, 21, 0, 1236, 1237, 1, 0, 0, 0, 1237, 1238, 6, 159, 10, 0, 1238, 333, 1, 0, 0, 0, 1239, 1240, 3, 58, 22, 0, 1240, 1241, 1, 0, 0, 0, 1241, 1242, 6, 160, 10, 0, 1242, 335, 1, 0, 0, 0, 1243, 1244, 3, 74, 30, 0, 1244, 1245, 1, 0, 0, 0, 1245, 1246, 6, 161, 13, 0, 1246, 1247, 6, 161, 14, 0, 1247, 337, 1, 0, 0, 0, 1248, 1249, 3, 62, 24, 0, 1249, 1250, 1, 0, 0, 0, 1250, 1251, 6, 162, 19, 0, 1251, 1252, 6, 162, 14, 0, 1252, 1253, 6, 162, 28, 0, 1253, 339, 1, 0, 0, 0, 1254, 1255, 3, 54, 20, 0, 1255, 1256, 1, 0, 0, 0, 1256, 1257, 6, 163, 10, 0, 1257, 341, 1, 0, 0, 0, 1258, 1259, 3, 56, 21, 0, 1259, 1260, 1, 0, 0, 0, 1260, 1261, 6, 164, 10, 0, 1261, 343, 1, 0, 0, 0, 1262, 1263, 3, 58, 22, 0, 1263, 1264, 1, 0, 0, 0, 1264, 1265, 6, 165, 10, 0, 1265, 345, 1, 0, 0, 0, 1266, 1267, 3, 112, 49, 0, 1267, 1268, 1, 0, 0, 0, 1268, 1269, 6, 166, 16, 0, 1269, 1270, 6, 166, 14, 0, 1270, 1271, 6, 166, 6, 0, 1271, 347, 1, 0, 0, 0, 1272, 1273, 3, 54, 20, 0, 1273, 1274, 1, 0, 0, 0, 1274, 1275, 6, 167, 10, 0, 1275, 349, 1, 0, 0, 0, 1276, 1277, 3, 56, 21, 0, 1277, 1278, 1, 0, 0, 0, 1278, 1279, 6, 168, 10, 0, 1279, 351, 1, 0, 0, 0, 1280, 1281, 3, 58, 22, 0, 1281, 1282, 1, 0, 0, 0, 1282, 1283, 6, 169, 10, 0, 1283, 353, 1, 0, 0, 0, 1284, 1285, 3, 180, 83, 0, 1285, 1286, 1, 0, 0, 0, 1286, 1287, 6, 170, 14, 0, 1287, 1288, 6, 170, 0, 0, 1288, 1289, 6, 170, 24, 0, 1289, 355, 1, 0, 0, 0, 1290, 1291, 3, 176, 81, 0, 1291, 1292, 1, 0, 0, 0, 1292, 1293, 6, 171, 14, 0, 1293, 1294, 6, 171, 0, 0, 1294, 1295, 6, 171, 27, 0, 1295, 357, 1, 0, 0, 0, 1296, 1297, 3, 102, 44, 0, 1297, 1298, 1, 0, 0, 0, 1298, 1299, 6, 172, 14, 0, 1299, 1300, 6, 172, 0, 0, 1300, 1301, 6, 172, 29, 0, 1301, 359, 1, 0, 0, 0, 1302, 1303, 3, 74, 30, 0, 1303, 1304, 1, 0, 0, 0, 1304, 1305, 6, 173, 13, 0, 1305, 1306, 6, 173, 14, 0, 1306, 361, 1, 0, 0, 0, 60, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 525, 535, 539, 542, 551, 553, 564, 571, 576, 615, 620, 629, 636, 641, 643, 654, 662, 665, 667, 672, 677, 683, 690, 695, 701, 704, 712, 716, 849, 854, 859, 861, 867, 960, 964, 969, 974, 979, 981, 985, 987, 1064, 1068, 1073, 1227, 1229, 30, 5, 2, 0, 5, 4, 0, 5, 6, 0, 5, 1, 0, 5, 3, 0, 5, 10, 0, 5, 12, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 0, 1, 0, 7, 67, 0, 5, 0, 0, 7, 28, 0, 4, 0, 0, 7, 68, 0, 7, 37, 0, 7, 35, 0, 7, 29, 0, 7, 24, 0, 7, 39, 0, 7, 79, 0, 5, 11, 0, 5, 7, 0, 7, 70, 0, 7, 89, 0, 7, 88, 0, 7, 69, 0, 5, 13, 0, 7, 32, 0] \ No newline at end of file +[4, 0, 116, 1297, 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, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 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, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 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, 13, 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, 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, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 4, 19, 522, 8, 19, 11, 19, 12, 19, 523, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 532, 8, 20, 10, 20, 12, 20, 535, 9, 20, 1, 20, 3, 20, 538, 8, 20, 1, 20, 3, 20, 541, 8, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 550, 8, 21, 10, 21, 12, 21, 553, 9, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 4, 22, 561, 8, 22, 11, 22, 12, 22, 562, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 3, 23, 570, 8, 23, 1, 24, 4, 24, 573, 8, 24, 11, 24, 12, 24, 574, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 35, 1, 35, 3, 35, 614, 8, 35, 1, 35, 4, 35, 617, 8, 35, 11, 35, 12, 35, 618, 1, 36, 1, 36, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 3, 38, 628, 8, 38, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 3, 40, 635, 8, 40, 1, 41, 1, 41, 1, 41, 5, 41, 640, 8, 41, 10, 41, 12, 41, 643, 9, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 5, 41, 651, 8, 41, 10, 41, 12, 41, 654, 9, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 661, 8, 41, 1, 41, 3, 41, 664, 8, 41, 3, 41, 666, 8, 41, 1, 42, 4, 42, 669, 8, 42, 11, 42, 12, 42, 670, 1, 43, 4, 43, 674, 8, 43, 11, 43, 12, 43, 675, 1, 43, 1, 43, 5, 43, 680, 8, 43, 10, 43, 12, 43, 683, 9, 43, 1, 43, 1, 43, 4, 43, 687, 8, 43, 11, 43, 12, 43, 688, 1, 43, 4, 43, 692, 8, 43, 11, 43, 12, 43, 693, 1, 43, 1, 43, 5, 43, 698, 8, 43, 10, 43, 12, 43, 701, 9, 43, 3, 43, 703, 8, 43, 1, 43, 1, 43, 1, 43, 1, 43, 4, 43, 709, 8, 43, 11, 43, 12, 43, 710, 1, 43, 1, 43, 3, 43, 715, 8, 43, 1, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, 74, 1, 74, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79, 1, 79, 1, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 5, 81, 846, 8, 81, 10, 81, 12, 81, 849, 9, 81, 1, 81, 1, 81, 3, 81, 853, 8, 81, 1, 81, 4, 81, 856, 8, 81, 11, 81, 12, 81, 857, 3, 81, 860, 8, 81, 1, 82, 1, 82, 4, 82, 864, 8, 82, 11, 82, 12, 82, 865, 1, 82, 1, 82, 1, 83, 1, 83, 1, 84, 1, 84, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 3, 101, 951, 8, 101, 1, 102, 1, 102, 3, 102, 955, 8, 102, 1, 102, 5, 102, 958, 8, 102, 10, 102, 12, 102, 961, 9, 102, 1, 102, 1, 102, 3, 102, 965, 8, 102, 1, 102, 4, 102, 968, 8, 102, 11, 102, 12, 102, 969, 3, 102, 972, 8, 102, 1, 103, 1, 103, 4, 103, 976, 8, 103, 11, 103, 12, 103, 977, 1, 104, 1, 104, 1, 104, 1, 104, 1, 105, 1, 105, 1, 105, 1, 105, 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, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 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, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 121, 4, 121, 1053, 8, 121, 11, 121, 12, 121, 1054, 1, 121, 1, 121, 3, 121, 1059, 8, 121, 1, 121, 4, 121, 1062, 8, 121, 11, 121, 12, 121, 1063, 1, 122, 1, 122, 1, 122, 1, 122, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 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, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 156, 4, 156, 1218, 8, 156, 11, 156, 12, 156, 1219, 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, 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, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 2, 551, 652, 0, 173, 14, 1, 16, 2, 18, 3, 20, 4, 22, 5, 24, 6, 26, 7, 28, 8, 30, 9, 32, 10, 34, 11, 36, 12, 38, 13, 40, 14, 42, 15, 44, 16, 46, 17, 48, 18, 50, 19, 52, 20, 54, 21, 56, 22, 58, 23, 60, 0, 62, 24, 64, 0, 66, 0, 68, 25, 70, 26, 72, 27, 74, 28, 76, 0, 78, 0, 80, 0, 82, 0, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 29, 98, 30, 100, 31, 102, 32, 104, 33, 106, 34, 108, 35, 110, 36, 112, 37, 114, 38, 116, 39, 118, 40, 120, 41, 122, 42, 124, 43, 126, 44, 128, 45, 130, 46, 132, 47, 134, 48, 136, 49, 138, 50, 140, 51, 142, 52, 144, 53, 146, 54, 148, 55, 150, 56, 152, 57, 154, 58, 156, 59, 158, 60, 160, 61, 162, 62, 164, 63, 166, 64, 168, 65, 170, 66, 172, 67, 174, 68, 176, 69, 178, 0, 180, 70, 182, 71, 184, 72, 186, 73, 188, 0, 190, 0, 192, 0, 194, 0, 196, 0, 198, 0, 200, 74, 202, 0, 204, 75, 206, 76, 208, 77, 210, 0, 212, 0, 214, 0, 216, 0, 218, 0, 220, 78, 222, 79, 224, 80, 226, 81, 228, 0, 230, 0, 232, 0, 234, 0, 236, 82, 238, 0, 240, 83, 242, 84, 244, 85, 246, 0, 248, 0, 250, 86, 252, 87, 254, 0, 256, 88, 258, 0, 260, 0, 262, 89, 264, 90, 266, 91, 268, 0, 270, 0, 272, 0, 274, 0, 276, 0, 278, 0, 280, 0, 282, 92, 284, 93, 286, 94, 288, 0, 290, 0, 292, 0, 294, 0, 296, 95, 298, 96, 300, 97, 302, 0, 304, 98, 306, 99, 308, 100, 310, 101, 312, 0, 314, 102, 316, 103, 318, 104, 320, 105, 322, 0, 324, 106, 326, 107, 328, 108, 330, 109, 332, 110, 334, 0, 336, 0, 338, 111, 340, 112, 342, 113, 344, 0, 346, 114, 348, 115, 350, 116, 352, 0, 354, 0, 356, 0, 358, 0, 14, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 13, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 10, 0, 9, 10, 13, 13, 32, 32, 44, 44, 47, 47, 61, 61, 91, 91, 93, 93, 96, 96, 124, 124, 2, 0, 42, 42, 47, 47, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 5, 0, 34, 34, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1322, 0, 14, 1, 0, 0, 0, 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, 62, 1, 0, 0, 0, 1, 64, 1, 0, 0, 0, 1, 66, 1, 0, 0, 0, 1, 68, 1, 0, 0, 0, 1, 70, 1, 0, 0, 0, 1, 72, 1, 0, 0, 0, 2, 74, 1, 0, 0, 0, 2, 96, 1, 0, 0, 0, 2, 98, 1, 0, 0, 0, 2, 100, 1, 0, 0, 0, 2, 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, 112, 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, 2, 124, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 2, 136, 1, 0, 0, 0, 2, 138, 1, 0, 0, 0, 2, 140, 1, 0, 0, 0, 2, 142, 1, 0, 0, 0, 2, 144, 1, 0, 0, 0, 2, 146, 1, 0, 0, 0, 2, 148, 1, 0, 0, 0, 2, 150, 1, 0, 0, 0, 2, 152, 1, 0, 0, 0, 2, 154, 1, 0, 0, 0, 2, 156, 1, 0, 0, 0, 2, 158, 1, 0, 0, 0, 2, 160, 1, 0, 0, 0, 2, 162, 1, 0, 0, 0, 2, 164, 1, 0, 0, 0, 2, 166, 1, 0, 0, 0, 2, 168, 1, 0, 0, 0, 2, 170, 1, 0, 0, 0, 2, 172, 1, 0, 0, 0, 2, 174, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 2, 180, 1, 0, 0, 0, 2, 182, 1, 0, 0, 0, 2, 184, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 3, 188, 1, 0, 0, 0, 3, 190, 1, 0, 0, 0, 3, 192, 1, 0, 0, 0, 3, 194, 1, 0, 0, 0, 3, 196, 1, 0, 0, 0, 3, 198, 1, 0, 0, 0, 3, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 208, 1, 0, 0, 0, 4, 210, 1, 0, 0, 0, 4, 212, 1, 0, 0, 0, 4, 214, 1, 0, 0, 0, 4, 220, 1, 0, 0, 0, 4, 222, 1, 0, 0, 0, 4, 224, 1, 0, 0, 0, 4, 226, 1, 0, 0, 0, 5, 228, 1, 0, 0, 0, 5, 230, 1, 0, 0, 0, 5, 232, 1, 0, 0, 0, 5, 234, 1, 0, 0, 0, 5, 236, 1, 0, 0, 0, 5, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 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, 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, 7, 268, 1, 0, 0, 0, 7, 270, 1, 0, 0, 0, 7, 272, 1, 0, 0, 0, 7, 274, 1, 0, 0, 0, 7, 276, 1, 0, 0, 0, 7, 278, 1, 0, 0, 0, 7, 280, 1, 0, 0, 0, 7, 282, 1, 0, 0, 0, 7, 284, 1, 0, 0, 0, 7, 286, 1, 0, 0, 0, 8, 288, 1, 0, 0, 0, 8, 290, 1, 0, 0, 0, 8, 292, 1, 0, 0, 0, 8, 294, 1, 0, 0, 0, 8, 296, 1, 0, 0, 0, 8, 298, 1, 0, 0, 0, 8, 300, 1, 0, 0, 0, 9, 302, 1, 0, 0, 0, 9, 304, 1, 0, 0, 0, 9, 306, 1, 0, 0, 0, 9, 308, 1, 0, 0, 0, 9, 310, 1, 0, 0, 0, 10, 312, 1, 0, 0, 0, 10, 314, 1, 0, 0, 0, 10, 316, 1, 0, 0, 0, 10, 318, 1, 0, 0, 0, 10, 320, 1, 0, 0, 0, 11, 322, 1, 0, 0, 0, 11, 324, 1, 0, 0, 0, 11, 326, 1, 0, 0, 0, 11, 328, 1, 0, 0, 0, 11, 330, 1, 0, 0, 0, 11, 332, 1, 0, 0, 0, 12, 334, 1, 0, 0, 0, 12, 336, 1, 0, 0, 0, 12, 338, 1, 0, 0, 0, 12, 340, 1, 0, 0, 0, 12, 342, 1, 0, 0, 0, 13, 344, 1, 0, 0, 0, 13, 346, 1, 0, 0, 0, 13, 348, 1, 0, 0, 0, 13, 350, 1, 0, 0, 0, 13, 352, 1, 0, 0, 0, 13, 354, 1, 0, 0, 0, 13, 356, 1, 0, 0, 0, 13, 358, 1, 0, 0, 0, 14, 360, 1, 0, 0, 0, 16, 370, 1, 0, 0, 0, 18, 377, 1, 0, 0, 0, 20, 386, 1, 0, 0, 0, 22, 393, 1, 0, 0, 0, 24, 403, 1, 0, 0, 0, 26, 410, 1, 0, 0, 0, 28, 417, 1, 0, 0, 0, 30, 431, 1, 0, 0, 0, 32, 438, 1, 0, 0, 0, 34, 446, 1, 0, 0, 0, 36, 453, 1, 0, 0, 0, 38, 463, 1, 0, 0, 0, 40, 475, 1, 0, 0, 0, 42, 484, 1, 0, 0, 0, 44, 490, 1, 0, 0, 0, 46, 497, 1, 0, 0, 0, 48, 504, 1, 0, 0, 0, 50, 512, 1, 0, 0, 0, 52, 521, 1, 0, 0, 0, 54, 527, 1, 0, 0, 0, 56, 544, 1, 0, 0, 0, 58, 560, 1, 0, 0, 0, 60, 569, 1, 0, 0, 0, 62, 572, 1, 0, 0, 0, 64, 576, 1, 0, 0, 0, 66, 581, 1, 0, 0, 0, 68, 586, 1, 0, 0, 0, 70, 590, 1, 0, 0, 0, 72, 594, 1, 0, 0, 0, 74, 598, 1, 0, 0, 0, 76, 602, 1, 0, 0, 0, 78, 604, 1, 0, 0, 0, 80, 606, 1, 0, 0, 0, 82, 609, 1, 0, 0, 0, 84, 611, 1, 0, 0, 0, 86, 620, 1, 0, 0, 0, 88, 622, 1, 0, 0, 0, 90, 627, 1, 0, 0, 0, 92, 629, 1, 0, 0, 0, 94, 634, 1, 0, 0, 0, 96, 665, 1, 0, 0, 0, 98, 668, 1, 0, 0, 0, 100, 714, 1, 0, 0, 0, 102, 716, 1, 0, 0, 0, 104, 719, 1, 0, 0, 0, 106, 723, 1, 0, 0, 0, 108, 727, 1, 0, 0, 0, 110, 729, 1, 0, 0, 0, 112, 732, 1, 0, 0, 0, 114, 734, 1, 0, 0, 0, 116, 739, 1, 0, 0, 0, 118, 741, 1, 0, 0, 0, 120, 747, 1, 0, 0, 0, 122, 753, 1, 0, 0, 0, 124, 758, 1, 0, 0, 0, 126, 760, 1, 0, 0, 0, 128, 763, 1, 0, 0, 0, 130, 766, 1, 0, 0, 0, 132, 771, 1, 0, 0, 0, 134, 775, 1, 0, 0, 0, 136, 780, 1, 0, 0, 0, 138, 786, 1, 0, 0, 0, 140, 789, 1, 0, 0, 0, 142, 791, 1, 0, 0, 0, 144, 797, 1, 0, 0, 0, 146, 799, 1, 0, 0, 0, 148, 804, 1, 0, 0, 0, 150, 807, 1, 0, 0, 0, 152, 810, 1, 0, 0, 0, 154, 813, 1, 0, 0, 0, 156, 815, 1, 0, 0, 0, 158, 818, 1, 0, 0, 0, 160, 820, 1, 0, 0, 0, 162, 823, 1, 0, 0, 0, 164, 825, 1, 0, 0, 0, 166, 827, 1, 0, 0, 0, 168, 829, 1, 0, 0, 0, 170, 831, 1, 0, 0, 0, 172, 833, 1, 0, 0, 0, 174, 838, 1, 0, 0, 0, 176, 859, 1, 0, 0, 0, 178, 861, 1, 0, 0, 0, 180, 869, 1, 0, 0, 0, 182, 871, 1, 0, 0, 0, 184, 875, 1, 0, 0, 0, 186, 879, 1, 0, 0, 0, 188, 883, 1, 0, 0, 0, 190, 888, 1, 0, 0, 0, 192, 892, 1, 0, 0, 0, 194, 896, 1, 0, 0, 0, 196, 900, 1, 0, 0, 0, 198, 904, 1, 0, 0, 0, 200, 908, 1, 0, 0, 0, 202, 917, 1, 0, 0, 0, 204, 921, 1, 0, 0, 0, 206, 925, 1, 0, 0, 0, 208, 929, 1, 0, 0, 0, 210, 933, 1, 0, 0, 0, 212, 938, 1, 0, 0, 0, 214, 942, 1, 0, 0, 0, 216, 950, 1, 0, 0, 0, 218, 971, 1, 0, 0, 0, 220, 975, 1, 0, 0, 0, 222, 979, 1, 0, 0, 0, 224, 983, 1, 0, 0, 0, 226, 987, 1, 0, 0, 0, 228, 991, 1, 0, 0, 0, 230, 996, 1, 0, 0, 0, 232, 1000, 1, 0, 0, 0, 234, 1004, 1, 0, 0, 0, 236, 1008, 1, 0, 0, 0, 238, 1011, 1, 0, 0, 0, 240, 1015, 1, 0, 0, 0, 242, 1019, 1, 0, 0, 0, 244, 1023, 1, 0, 0, 0, 246, 1027, 1, 0, 0, 0, 248, 1032, 1, 0, 0, 0, 250, 1037, 1, 0, 0, 0, 252, 1042, 1, 0, 0, 0, 254, 1049, 1, 0, 0, 0, 256, 1058, 1, 0, 0, 0, 258, 1065, 1, 0, 0, 0, 260, 1069, 1, 0, 0, 0, 262, 1073, 1, 0, 0, 0, 264, 1077, 1, 0, 0, 0, 266, 1081, 1, 0, 0, 0, 268, 1085, 1, 0, 0, 0, 270, 1091, 1, 0, 0, 0, 272, 1095, 1, 0, 0, 0, 274, 1099, 1, 0, 0, 0, 276, 1103, 1, 0, 0, 0, 278, 1107, 1, 0, 0, 0, 280, 1111, 1, 0, 0, 0, 282, 1115, 1, 0, 0, 0, 284, 1119, 1, 0, 0, 0, 286, 1123, 1, 0, 0, 0, 288, 1127, 1, 0, 0, 0, 290, 1132, 1, 0, 0, 0, 292, 1136, 1, 0, 0, 0, 294, 1140, 1, 0, 0, 0, 296, 1144, 1, 0, 0, 0, 298, 1148, 1, 0, 0, 0, 300, 1152, 1, 0, 0, 0, 302, 1156, 1, 0, 0, 0, 304, 1161, 1, 0, 0, 0, 306, 1166, 1, 0, 0, 0, 308, 1170, 1, 0, 0, 0, 310, 1174, 1, 0, 0, 0, 312, 1178, 1, 0, 0, 0, 314, 1183, 1, 0, 0, 0, 316, 1193, 1, 0, 0, 0, 318, 1197, 1, 0, 0, 0, 320, 1201, 1, 0, 0, 0, 322, 1205, 1, 0, 0, 0, 324, 1210, 1, 0, 0, 0, 326, 1217, 1, 0, 0, 0, 328, 1221, 1, 0, 0, 0, 330, 1225, 1, 0, 0, 0, 332, 1229, 1, 0, 0, 0, 334, 1233, 1, 0, 0, 0, 336, 1238, 1, 0, 0, 0, 338, 1244, 1, 0, 0, 0, 340, 1248, 1, 0, 0, 0, 342, 1252, 1, 0, 0, 0, 344, 1256, 1, 0, 0, 0, 346, 1262, 1, 0, 0, 0, 348, 1266, 1, 0, 0, 0, 350, 1270, 1, 0, 0, 0, 352, 1274, 1, 0, 0, 0, 354, 1280, 1, 0, 0, 0, 356, 1286, 1, 0, 0, 0, 358, 1292, 1, 0, 0, 0, 360, 361, 5, 100, 0, 0, 361, 362, 5, 105, 0, 0, 362, 363, 5, 115, 0, 0, 363, 364, 5, 115, 0, 0, 364, 365, 5, 101, 0, 0, 365, 366, 5, 99, 0, 0, 366, 367, 5, 116, 0, 0, 367, 368, 1, 0, 0, 0, 368, 369, 6, 0, 0, 0, 369, 15, 1, 0, 0, 0, 370, 371, 5, 100, 0, 0, 371, 372, 5, 114, 0, 0, 372, 373, 5, 111, 0, 0, 373, 374, 5, 112, 0, 0, 374, 375, 1, 0, 0, 0, 375, 376, 6, 1, 1, 0, 376, 17, 1, 0, 0, 0, 377, 378, 5, 101, 0, 0, 378, 379, 5, 110, 0, 0, 379, 380, 5, 114, 0, 0, 380, 381, 5, 105, 0, 0, 381, 382, 5, 99, 0, 0, 382, 383, 5, 104, 0, 0, 383, 384, 1, 0, 0, 0, 384, 385, 6, 2, 2, 0, 385, 19, 1, 0, 0, 0, 386, 387, 5, 101, 0, 0, 387, 388, 5, 118, 0, 0, 388, 389, 5, 97, 0, 0, 389, 390, 5, 108, 0, 0, 390, 391, 1, 0, 0, 0, 391, 392, 6, 3, 0, 0, 392, 21, 1, 0, 0, 0, 393, 394, 5, 101, 0, 0, 394, 395, 5, 120, 0, 0, 395, 396, 5, 112, 0, 0, 396, 397, 5, 108, 0, 0, 397, 398, 5, 97, 0, 0, 398, 399, 5, 105, 0, 0, 399, 400, 5, 110, 0, 0, 400, 401, 1, 0, 0, 0, 401, 402, 6, 4, 3, 0, 402, 23, 1, 0, 0, 0, 403, 404, 5, 102, 0, 0, 404, 405, 5, 114, 0, 0, 405, 406, 5, 111, 0, 0, 406, 407, 5, 109, 0, 0, 407, 408, 1, 0, 0, 0, 408, 409, 6, 5, 4, 0, 409, 25, 1, 0, 0, 0, 410, 411, 5, 103, 0, 0, 411, 412, 5, 114, 0, 0, 412, 413, 5, 111, 0, 0, 413, 414, 5, 107, 0, 0, 414, 415, 1, 0, 0, 0, 415, 416, 6, 6, 0, 0, 416, 27, 1, 0, 0, 0, 417, 418, 5, 105, 0, 0, 418, 419, 5, 110, 0, 0, 419, 420, 5, 108, 0, 0, 420, 421, 5, 105, 0, 0, 421, 422, 5, 110, 0, 0, 422, 423, 5, 101, 0, 0, 423, 424, 5, 115, 0, 0, 424, 425, 5, 116, 0, 0, 425, 426, 5, 97, 0, 0, 426, 427, 5, 116, 0, 0, 427, 428, 5, 115, 0, 0, 428, 429, 1, 0, 0, 0, 429, 430, 6, 7, 0, 0, 430, 29, 1, 0, 0, 0, 431, 432, 5, 107, 0, 0, 432, 433, 5, 101, 0, 0, 433, 434, 5, 101, 0, 0, 434, 435, 5, 112, 0, 0, 435, 436, 1, 0, 0, 0, 436, 437, 6, 8, 1, 0, 437, 31, 1, 0, 0, 0, 438, 439, 5, 108, 0, 0, 439, 440, 5, 105, 0, 0, 440, 441, 5, 109, 0, 0, 441, 442, 5, 105, 0, 0, 442, 443, 5, 116, 0, 0, 443, 444, 1, 0, 0, 0, 444, 445, 6, 9, 0, 0, 445, 33, 1, 0, 0, 0, 446, 447, 5, 109, 0, 0, 447, 448, 5, 101, 0, 0, 448, 449, 5, 116, 0, 0, 449, 450, 5, 97, 0, 0, 450, 451, 1, 0, 0, 0, 451, 452, 6, 10, 5, 0, 452, 35, 1, 0, 0, 0, 453, 454, 5, 109, 0, 0, 454, 455, 5, 101, 0, 0, 455, 456, 5, 116, 0, 0, 456, 457, 5, 114, 0, 0, 457, 458, 5, 105, 0, 0, 458, 459, 5, 99, 0, 0, 459, 460, 5, 115, 0, 0, 460, 461, 1, 0, 0, 0, 461, 462, 6, 11, 6, 0, 462, 37, 1, 0, 0, 0, 463, 464, 5, 109, 0, 0, 464, 465, 5, 118, 0, 0, 465, 466, 5, 95, 0, 0, 466, 467, 5, 101, 0, 0, 467, 468, 5, 120, 0, 0, 468, 469, 5, 112, 0, 0, 469, 470, 5, 97, 0, 0, 470, 471, 5, 110, 0, 0, 471, 472, 5, 100, 0, 0, 472, 473, 1, 0, 0, 0, 473, 474, 6, 12, 7, 0, 474, 39, 1, 0, 0, 0, 475, 476, 5, 114, 0, 0, 476, 477, 5, 101, 0, 0, 477, 478, 5, 110, 0, 0, 478, 479, 5, 97, 0, 0, 479, 480, 5, 109, 0, 0, 480, 481, 5, 101, 0, 0, 481, 482, 1, 0, 0, 0, 482, 483, 6, 13, 8, 0, 483, 41, 1, 0, 0, 0, 484, 485, 5, 114, 0, 0, 485, 486, 5, 111, 0, 0, 486, 487, 5, 119, 0, 0, 487, 488, 1, 0, 0, 0, 488, 489, 6, 14, 0, 0, 489, 43, 1, 0, 0, 0, 490, 491, 5, 115, 0, 0, 491, 492, 5, 104, 0, 0, 492, 493, 5, 111, 0, 0, 493, 494, 5, 119, 0, 0, 494, 495, 1, 0, 0, 0, 495, 496, 6, 15, 9, 0, 496, 45, 1, 0, 0, 0, 497, 498, 5, 115, 0, 0, 498, 499, 5, 111, 0, 0, 499, 500, 5, 114, 0, 0, 500, 501, 5, 116, 0, 0, 501, 502, 1, 0, 0, 0, 502, 503, 6, 16, 0, 0, 503, 47, 1, 0, 0, 0, 504, 505, 5, 115, 0, 0, 505, 506, 5, 116, 0, 0, 506, 507, 5, 97, 0, 0, 507, 508, 5, 116, 0, 0, 508, 509, 5, 115, 0, 0, 509, 510, 1, 0, 0, 0, 510, 511, 6, 17, 0, 0, 511, 49, 1, 0, 0, 0, 512, 513, 5, 119, 0, 0, 513, 514, 5, 104, 0, 0, 514, 515, 5, 101, 0, 0, 515, 516, 5, 114, 0, 0, 516, 517, 5, 101, 0, 0, 517, 518, 1, 0, 0, 0, 518, 519, 6, 18, 0, 0, 519, 51, 1, 0, 0, 0, 520, 522, 8, 0, 0, 0, 521, 520, 1, 0, 0, 0, 522, 523, 1, 0, 0, 0, 523, 521, 1, 0, 0, 0, 523, 524, 1, 0, 0, 0, 524, 525, 1, 0, 0, 0, 525, 526, 6, 19, 0, 0, 526, 53, 1, 0, 0, 0, 527, 528, 5, 47, 0, 0, 528, 529, 5, 47, 0, 0, 529, 533, 1, 0, 0, 0, 530, 532, 8, 1, 0, 0, 531, 530, 1, 0, 0, 0, 532, 535, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 533, 534, 1, 0, 0, 0, 534, 537, 1, 0, 0, 0, 535, 533, 1, 0, 0, 0, 536, 538, 5, 13, 0, 0, 537, 536, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 540, 1, 0, 0, 0, 539, 541, 5, 10, 0, 0, 540, 539, 1, 0, 0, 0, 540, 541, 1, 0, 0, 0, 541, 542, 1, 0, 0, 0, 542, 543, 6, 20, 10, 0, 543, 55, 1, 0, 0, 0, 544, 545, 5, 47, 0, 0, 545, 546, 5, 42, 0, 0, 546, 551, 1, 0, 0, 0, 547, 550, 3, 56, 21, 0, 548, 550, 9, 0, 0, 0, 549, 547, 1, 0, 0, 0, 549, 548, 1, 0, 0, 0, 550, 553, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 551, 549, 1, 0, 0, 0, 552, 554, 1, 0, 0, 0, 553, 551, 1, 0, 0, 0, 554, 555, 5, 42, 0, 0, 555, 556, 5, 47, 0, 0, 556, 557, 1, 0, 0, 0, 557, 558, 6, 21, 10, 0, 558, 57, 1, 0, 0, 0, 559, 561, 7, 2, 0, 0, 560, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, 560, 1, 0, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 1, 0, 0, 0, 564, 565, 6, 22, 10, 0, 565, 59, 1, 0, 0, 0, 566, 570, 8, 3, 0, 0, 567, 568, 5, 47, 0, 0, 568, 570, 8, 4, 0, 0, 569, 566, 1, 0, 0, 0, 569, 567, 1, 0, 0, 0, 570, 61, 1, 0, 0, 0, 571, 573, 3, 60, 23, 0, 572, 571, 1, 0, 0, 0, 573, 574, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 63, 1, 0, 0, 0, 576, 577, 3, 172, 79, 0, 577, 578, 1, 0, 0, 0, 578, 579, 6, 25, 11, 0, 579, 580, 6, 25, 12, 0, 580, 65, 1, 0, 0, 0, 581, 582, 3, 74, 30, 0, 582, 583, 1, 0, 0, 0, 583, 584, 6, 26, 13, 0, 584, 585, 6, 26, 14, 0, 585, 67, 1, 0, 0, 0, 586, 587, 3, 58, 22, 0, 587, 588, 1, 0, 0, 0, 588, 589, 6, 27, 10, 0, 589, 69, 1, 0, 0, 0, 590, 591, 3, 54, 20, 0, 591, 592, 1, 0, 0, 0, 592, 593, 6, 28, 10, 0, 593, 71, 1, 0, 0, 0, 594, 595, 3, 56, 21, 0, 595, 596, 1, 0, 0, 0, 596, 597, 6, 29, 10, 0, 597, 73, 1, 0, 0, 0, 598, 599, 5, 124, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 6, 30, 14, 0, 601, 75, 1, 0, 0, 0, 602, 603, 7, 5, 0, 0, 603, 77, 1, 0, 0, 0, 604, 605, 7, 6, 0, 0, 605, 79, 1, 0, 0, 0, 606, 607, 5, 92, 0, 0, 607, 608, 7, 7, 0, 0, 608, 81, 1, 0, 0, 0, 609, 610, 8, 8, 0, 0, 610, 83, 1, 0, 0, 0, 611, 613, 7, 9, 0, 0, 612, 614, 7, 10, 0, 0, 613, 612, 1, 0, 0, 0, 613, 614, 1, 0, 0, 0, 614, 616, 1, 0, 0, 0, 615, 617, 3, 76, 31, 0, 616, 615, 1, 0, 0, 0, 617, 618, 1, 0, 0, 0, 618, 616, 1, 0, 0, 0, 618, 619, 1, 0, 0, 0, 619, 85, 1, 0, 0, 0, 620, 621, 5, 64, 0, 0, 621, 87, 1, 0, 0, 0, 622, 623, 5, 96, 0, 0, 623, 89, 1, 0, 0, 0, 624, 628, 8, 11, 0, 0, 625, 626, 5, 96, 0, 0, 626, 628, 5, 96, 0, 0, 627, 624, 1, 0, 0, 0, 627, 625, 1, 0, 0, 0, 628, 91, 1, 0, 0, 0, 629, 630, 5, 95, 0, 0, 630, 93, 1, 0, 0, 0, 631, 635, 3, 78, 32, 0, 632, 635, 3, 76, 31, 0, 633, 635, 3, 92, 39, 0, 634, 631, 1, 0, 0, 0, 634, 632, 1, 0, 0, 0, 634, 633, 1, 0, 0, 0, 635, 95, 1, 0, 0, 0, 636, 641, 5, 34, 0, 0, 637, 640, 3, 80, 33, 0, 638, 640, 3, 82, 34, 0, 639, 637, 1, 0, 0, 0, 639, 638, 1, 0, 0, 0, 640, 643, 1, 0, 0, 0, 641, 639, 1, 0, 0, 0, 641, 642, 1, 0, 0, 0, 642, 644, 1, 0, 0, 0, 643, 641, 1, 0, 0, 0, 644, 666, 5, 34, 0, 0, 645, 646, 5, 34, 0, 0, 646, 647, 5, 34, 0, 0, 647, 648, 5, 34, 0, 0, 648, 652, 1, 0, 0, 0, 649, 651, 8, 1, 0, 0, 650, 649, 1, 0, 0, 0, 651, 654, 1, 0, 0, 0, 652, 653, 1, 0, 0, 0, 652, 650, 1, 0, 0, 0, 653, 655, 1, 0, 0, 0, 654, 652, 1, 0, 0, 0, 655, 656, 5, 34, 0, 0, 656, 657, 5, 34, 0, 0, 657, 658, 5, 34, 0, 0, 658, 660, 1, 0, 0, 0, 659, 661, 5, 34, 0, 0, 660, 659, 1, 0, 0, 0, 660, 661, 1, 0, 0, 0, 661, 663, 1, 0, 0, 0, 662, 664, 5, 34, 0, 0, 663, 662, 1, 0, 0, 0, 663, 664, 1, 0, 0, 0, 664, 666, 1, 0, 0, 0, 665, 636, 1, 0, 0, 0, 665, 645, 1, 0, 0, 0, 666, 97, 1, 0, 0, 0, 667, 669, 3, 76, 31, 0, 668, 667, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 668, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 99, 1, 0, 0, 0, 672, 674, 3, 76, 31, 0, 673, 672, 1, 0, 0, 0, 674, 675, 1, 0, 0, 0, 675, 673, 1, 0, 0, 0, 675, 676, 1, 0, 0, 0, 676, 677, 1, 0, 0, 0, 677, 681, 3, 116, 51, 0, 678, 680, 3, 76, 31, 0, 679, 678, 1, 0, 0, 0, 680, 683, 1, 0, 0, 0, 681, 679, 1, 0, 0, 0, 681, 682, 1, 0, 0, 0, 682, 715, 1, 0, 0, 0, 683, 681, 1, 0, 0, 0, 684, 686, 3, 116, 51, 0, 685, 687, 3, 76, 31, 0, 686, 685, 1, 0, 0, 0, 687, 688, 1, 0, 0, 0, 688, 686, 1, 0, 0, 0, 688, 689, 1, 0, 0, 0, 689, 715, 1, 0, 0, 0, 690, 692, 3, 76, 31, 0, 691, 690, 1, 0, 0, 0, 692, 693, 1, 0, 0, 0, 693, 691, 1, 0, 0, 0, 693, 694, 1, 0, 0, 0, 694, 702, 1, 0, 0, 0, 695, 699, 3, 116, 51, 0, 696, 698, 3, 76, 31, 0, 697, 696, 1, 0, 0, 0, 698, 701, 1, 0, 0, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 703, 1, 0, 0, 0, 701, 699, 1, 0, 0, 0, 702, 695, 1, 0, 0, 0, 702, 703, 1, 0, 0, 0, 703, 704, 1, 0, 0, 0, 704, 705, 3, 84, 35, 0, 705, 715, 1, 0, 0, 0, 706, 708, 3, 116, 51, 0, 707, 709, 3, 76, 31, 0, 708, 707, 1, 0, 0, 0, 709, 710, 1, 0, 0, 0, 710, 708, 1, 0, 0, 0, 710, 711, 1, 0, 0, 0, 711, 712, 1, 0, 0, 0, 712, 713, 3, 84, 35, 0, 713, 715, 1, 0, 0, 0, 714, 673, 1, 0, 0, 0, 714, 684, 1, 0, 0, 0, 714, 691, 1, 0, 0, 0, 714, 706, 1, 0, 0, 0, 715, 101, 1, 0, 0, 0, 716, 717, 5, 98, 0, 0, 717, 718, 5, 121, 0, 0, 718, 103, 1, 0, 0, 0, 719, 720, 5, 97, 0, 0, 720, 721, 5, 110, 0, 0, 721, 722, 5, 100, 0, 0, 722, 105, 1, 0, 0, 0, 723, 724, 5, 97, 0, 0, 724, 725, 5, 115, 0, 0, 725, 726, 5, 99, 0, 0, 726, 107, 1, 0, 0, 0, 727, 728, 5, 61, 0, 0, 728, 109, 1, 0, 0, 0, 729, 730, 5, 58, 0, 0, 730, 731, 5, 58, 0, 0, 731, 111, 1, 0, 0, 0, 732, 733, 5, 44, 0, 0, 733, 113, 1, 0, 0, 0, 734, 735, 5, 100, 0, 0, 735, 736, 5, 101, 0, 0, 736, 737, 5, 115, 0, 0, 737, 738, 5, 99, 0, 0, 738, 115, 1, 0, 0, 0, 739, 740, 5, 46, 0, 0, 740, 117, 1, 0, 0, 0, 741, 742, 5, 102, 0, 0, 742, 743, 5, 97, 0, 0, 743, 744, 5, 108, 0, 0, 744, 745, 5, 115, 0, 0, 745, 746, 5, 101, 0, 0, 746, 119, 1, 0, 0, 0, 747, 748, 5, 102, 0, 0, 748, 749, 5, 105, 0, 0, 749, 750, 5, 114, 0, 0, 750, 751, 5, 115, 0, 0, 751, 752, 5, 116, 0, 0, 752, 121, 1, 0, 0, 0, 753, 754, 5, 108, 0, 0, 754, 755, 5, 97, 0, 0, 755, 756, 5, 115, 0, 0, 756, 757, 5, 116, 0, 0, 757, 123, 1, 0, 0, 0, 758, 759, 5, 40, 0, 0, 759, 125, 1, 0, 0, 0, 760, 761, 5, 105, 0, 0, 761, 762, 5, 110, 0, 0, 762, 127, 1, 0, 0, 0, 763, 764, 5, 105, 0, 0, 764, 765, 5, 115, 0, 0, 765, 129, 1, 0, 0, 0, 766, 767, 5, 108, 0, 0, 767, 768, 5, 105, 0, 0, 768, 769, 5, 107, 0, 0, 769, 770, 5, 101, 0, 0, 770, 131, 1, 0, 0, 0, 771, 772, 5, 110, 0, 0, 772, 773, 5, 111, 0, 0, 773, 774, 5, 116, 0, 0, 774, 133, 1, 0, 0, 0, 775, 776, 5, 110, 0, 0, 776, 777, 5, 117, 0, 0, 777, 778, 5, 108, 0, 0, 778, 779, 5, 108, 0, 0, 779, 135, 1, 0, 0, 0, 780, 781, 5, 110, 0, 0, 781, 782, 5, 117, 0, 0, 782, 783, 5, 108, 0, 0, 783, 784, 5, 108, 0, 0, 784, 785, 5, 115, 0, 0, 785, 137, 1, 0, 0, 0, 786, 787, 5, 111, 0, 0, 787, 788, 5, 114, 0, 0, 788, 139, 1, 0, 0, 0, 789, 790, 5, 63, 0, 0, 790, 141, 1, 0, 0, 0, 791, 792, 5, 114, 0, 0, 792, 793, 5, 108, 0, 0, 793, 794, 5, 105, 0, 0, 794, 795, 5, 107, 0, 0, 795, 796, 5, 101, 0, 0, 796, 143, 1, 0, 0, 0, 797, 798, 5, 41, 0, 0, 798, 145, 1, 0, 0, 0, 799, 800, 5, 116, 0, 0, 800, 801, 5, 114, 0, 0, 801, 802, 5, 117, 0, 0, 802, 803, 5, 101, 0, 0, 803, 147, 1, 0, 0, 0, 804, 805, 5, 61, 0, 0, 805, 806, 5, 61, 0, 0, 806, 149, 1, 0, 0, 0, 807, 808, 5, 61, 0, 0, 808, 809, 5, 126, 0, 0, 809, 151, 1, 0, 0, 0, 810, 811, 5, 33, 0, 0, 811, 812, 5, 61, 0, 0, 812, 153, 1, 0, 0, 0, 813, 814, 5, 60, 0, 0, 814, 155, 1, 0, 0, 0, 815, 816, 5, 60, 0, 0, 816, 817, 5, 61, 0, 0, 817, 157, 1, 0, 0, 0, 818, 819, 5, 62, 0, 0, 819, 159, 1, 0, 0, 0, 820, 821, 5, 62, 0, 0, 821, 822, 5, 61, 0, 0, 822, 161, 1, 0, 0, 0, 823, 824, 5, 43, 0, 0, 824, 163, 1, 0, 0, 0, 825, 826, 5, 45, 0, 0, 826, 165, 1, 0, 0, 0, 827, 828, 5, 42, 0, 0, 828, 167, 1, 0, 0, 0, 829, 830, 5, 47, 0, 0, 830, 169, 1, 0, 0, 0, 831, 832, 5, 37, 0, 0, 832, 171, 1, 0, 0, 0, 833, 834, 5, 91, 0, 0, 834, 835, 1, 0, 0, 0, 835, 836, 6, 79, 0, 0, 836, 837, 6, 79, 0, 0, 837, 173, 1, 0, 0, 0, 838, 839, 5, 93, 0, 0, 839, 840, 1, 0, 0, 0, 840, 841, 6, 80, 14, 0, 841, 842, 6, 80, 14, 0, 842, 175, 1, 0, 0, 0, 843, 847, 3, 78, 32, 0, 844, 846, 3, 94, 40, 0, 845, 844, 1, 0, 0, 0, 846, 849, 1, 0, 0, 0, 847, 845, 1, 0, 0, 0, 847, 848, 1, 0, 0, 0, 848, 860, 1, 0, 0, 0, 849, 847, 1, 0, 0, 0, 850, 853, 3, 92, 39, 0, 851, 853, 3, 86, 36, 0, 852, 850, 1, 0, 0, 0, 852, 851, 1, 0, 0, 0, 853, 855, 1, 0, 0, 0, 854, 856, 3, 94, 40, 0, 855, 854, 1, 0, 0, 0, 856, 857, 1, 0, 0, 0, 857, 855, 1, 0, 0, 0, 857, 858, 1, 0, 0, 0, 858, 860, 1, 0, 0, 0, 859, 843, 1, 0, 0, 0, 859, 852, 1, 0, 0, 0, 860, 177, 1, 0, 0, 0, 861, 863, 3, 88, 37, 0, 862, 864, 3, 90, 38, 0, 863, 862, 1, 0, 0, 0, 864, 865, 1, 0, 0, 0, 865, 863, 1, 0, 0, 0, 865, 866, 1, 0, 0, 0, 866, 867, 1, 0, 0, 0, 867, 868, 3, 88, 37, 0, 868, 179, 1, 0, 0, 0, 869, 870, 3, 178, 82, 0, 870, 181, 1, 0, 0, 0, 871, 872, 3, 54, 20, 0, 872, 873, 1, 0, 0, 0, 873, 874, 6, 84, 10, 0, 874, 183, 1, 0, 0, 0, 875, 876, 3, 56, 21, 0, 876, 877, 1, 0, 0, 0, 877, 878, 6, 85, 10, 0, 878, 185, 1, 0, 0, 0, 879, 880, 3, 58, 22, 0, 880, 881, 1, 0, 0, 0, 881, 882, 6, 86, 10, 0, 882, 187, 1, 0, 0, 0, 883, 884, 3, 74, 30, 0, 884, 885, 1, 0, 0, 0, 885, 886, 6, 87, 13, 0, 886, 887, 6, 87, 14, 0, 887, 189, 1, 0, 0, 0, 888, 889, 3, 172, 79, 0, 889, 890, 1, 0, 0, 0, 890, 891, 6, 88, 11, 0, 891, 191, 1, 0, 0, 0, 892, 893, 3, 174, 80, 0, 893, 894, 1, 0, 0, 0, 894, 895, 6, 89, 15, 0, 895, 193, 1, 0, 0, 0, 896, 897, 3, 112, 49, 0, 897, 898, 1, 0, 0, 0, 898, 899, 6, 90, 16, 0, 899, 195, 1, 0, 0, 0, 900, 901, 3, 108, 47, 0, 901, 902, 1, 0, 0, 0, 902, 903, 6, 91, 17, 0, 903, 197, 1, 0, 0, 0, 904, 905, 3, 96, 41, 0, 905, 906, 1, 0, 0, 0, 906, 907, 6, 92, 18, 0, 907, 199, 1, 0, 0, 0, 908, 909, 5, 109, 0, 0, 909, 910, 5, 101, 0, 0, 910, 911, 5, 116, 0, 0, 911, 912, 5, 97, 0, 0, 912, 913, 5, 100, 0, 0, 913, 914, 5, 97, 0, 0, 914, 915, 5, 116, 0, 0, 915, 916, 5, 97, 0, 0, 916, 201, 1, 0, 0, 0, 917, 918, 3, 62, 24, 0, 918, 919, 1, 0, 0, 0, 919, 920, 6, 94, 19, 0, 920, 203, 1, 0, 0, 0, 921, 922, 3, 54, 20, 0, 922, 923, 1, 0, 0, 0, 923, 924, 6, 95, 10, 0, 924, 205, 1, 0, 0, 0, 925, 926, 3, 56, 21, 0, 926, 927, 1, 0, 0, 0, 927, 928, 6, 96, 10, 0, 928, 207, 1, 0, 0, 0, 929, 930, 3, 58, 22, 0, 930, 931, 1, 0, 0, 0, 931, 932, 6, 97, 10, 0, 932, 209, 1, 0, 0, 0, 933, 934, 3, 74, 30, 0, 934, 935, 1, 0, 0, 0, 935, 936, 6, 98, 13, 0, 936, 937, 6, 98, 14, 0, 937, 211, 1, 0, 0, 0, 938, 939, 3, 116, 51, 0, 939, 940, 1, 0, 0, 0, 940, 941, 6, 99, 20, 0, 941, 213, 1, 0, 0, 0, 942, 943, 3, 112, 49, 0, 943, 944, 1, 0, 0, 0, 944, 945, 6, 100, 16, 0, 945, 215, 1, 0, 0, 0, 946, 951, 3, 78, 32, 0, 947, 951, 3, 76, 31, 0, 948, 951, 3, 92, 39, 0, 949, 951, 3, 166, 76, 0, 950, 946, 1, 0, 0, 0, 950, 947, 1, 0, 0, 0, 950, 948, 1, 0, 0, 0, 950, 949, 1, 0, 0, 0, 951, 217, 1, 0, 0, 0, 952, 955, 3, 78, 32, 0, 953, 955, 3, 166, 76, 0, 954, 952, 1, 0, 0, 0, 954, 953, 1, 0, 0, 0, 955, 959, 1, 0, 0, 0, 956, 958, 3, 216, 101, 0, 957, 956, 1, 0, 0, 0, 958, 961, 1, 0, 0, 0, 959, 957, 1, 0, 0, 0, 959, 960, 1, 0, 0, 0, 960, 972, 1, 0, 0, 0, 961, 959, 1, 0, 0, 0, 962, 965, 3, 92, 39, 0, 963, 965, 3, 86, 36, 0, 964, 962, 1, 0, 0, 0, 964, 963, 1, 0, 0, 0, 965, 967, 1, 0, 0, 0, 966, 968, 3, 216, 101, 0, 967, 966, 1, 0, 0, 0, 968, 969, 1, 0, 0, 0, 969, 967, 1, 0, 0, 0, 969, 970, 1, 0, 0, 0, 970, 972, 1, 0, 0, 0, 971, 954, 1, 0, 0, 0, 971, 964, 1, 0, 0, 0, 972, 219, 1, 0, 0, 0, 973, 976, 3, 218, 102, 0, 974, 976, 3, 178, 82, 0, 975, 973, 1, 0, 0, 0, 975, 974, 1, 0, 0, 0, 976, 977, 1, 0, 0, 0, 977, 975, 1, 0, 0, 0, 977, 978, 1, 0, 0, 0, 978, 221, 1, 0, 0, 0, 979, 980, 3, 54, 20, 0, 980, 981, 1, 0, 0, 0, 981, 982, 6, 104, 10, 0, 982, 223, 1, 0, 0, 0, 983, 984, 3, 56, 21, 0, 984, 985, 1, 0, 0, 0, 985, 986, 6, 105, 10, 0, 986, 225, 1, 0, 0, 0, 987, 988, 3, 58, 22, 0, 988, 989, 1, 0, 0, 0, 989, 990, 6, 106, 10, 0, 990, 227, 1, 0, 0, 0, 991, 992, 3, 74, 30, 0, 992, 993, 1, 0, 0, 0, 993, 994, 6, 107, 13, 0, 994, 995, 6, 107, 14, 0, 995, 229, 1, 0, 0, 0, 996, 997, 3, 108, 47, 0, 997, 998, 1, 0, 0, 0, 998, 999, 6, 108, 17, 0, 999, 231, 1, 0, 0, 0, 1000, 1001, 3, 112, 49, 0, 1001, 1002, 1, 0, 0, 0, 1002, 1003, 6, 109, 16, 0, 1003, 233, 1, 0, 0, 0, 1004, 1005, 3, 116, 51, 0, 1005, 1006, 1, 0, 0, 0, 1006, 1007, 6, 110, 20, 0, 1007, 235, 1, 0, 0, 0, 1008, 1009, 5, 97, 0, 0, 1009, 1010, 5, 115, 0, 0, 1010, 237, 1, 0, 0, 0, 1011, 1012, 3, 220, 103, 0, 1012, 1013, 1, 0, 0, 0, 1013, 1014, 6, 112, 21, 0, 1014, 239, 1, 0, 0, 0, 1015, 1016, 3, 54, 20, 0, 1016, 1017, 1, 0, 0, 0, 1017, 1018, 6, 113, 10, 0, 1018, 241, 1, 0, 0, 0, 1019, 1020, 3, 56, 21, 0, 1020, 1021, 1, 0, 0, 0, 1021, 1022, 6, 114, 10, 0, 1022, 243, 1, 0, 0, 0, 1023, 1024, 3, 58, 22, 0, 1024, 1025, 1, 0, 0, 0, 1025, 1026, 6, 115, 10, 0, 1026, 245, 1, 0, 0, 0, 1027, 1028, 3, 74, 30, 0, 1028, 1029, 1, 0, 0, 0, 1029, 1030, 6, 116, 13, 0, 1030, 1031, 6, 116, 14, 0, 1031, 247, 1, 0, 0, 0, 1032, 1033, 3, 172, 79, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 6, 117, 11, 0, 1035, 1036, 6, 117, 22, 0, 1036, 249, 1, 0, 0, 0, 1037, 1038, 5, 111, 0, 0, 1038, 1039, 5, 110, 0, 0, 1039, 1040, 1, 0, 0, 0, 1040, 1041, 6, 118, 23, 0, 1041, 251, 1, 0, 0, 0, 1042, 1043, 5, 119, 0, 0, 1043, 1044, 5, 105, 0, 0, 1044, 1045, 5, 116, 0, 0, 1045, 1046, 5, 104, 0, 0, 1046, 1047, 1, 0, 0, 0, 1047, 1048, 6, 119, 23, 0, 1048, 253, 1, 0, 0, 0, 1049, 1050, 8, 12, 0, 0, 1050, 255, 1, 0, 0, 0, 1051, 1053, 3, 254, 120, 0, 1052, 1051, 1, 0, 0, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1052, 1, 0, 0, 0, 1054, 1055, 1, 0, 0, 0, 1055, 1056, 1, 0, 0, 0, 1056, 1057, 3, 324, 155, 0, 1057, 1059, 1, 0, 0, 0, 1058, 1052, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1061, 1, 0, 0, 0, 1060, 1062, 3, 254, 120, 0, 1061, 1060, 1, 0, 0, 0, 1062, 1063, 1, 0, 0, 0, 1063, 1061, 1, 0, 0, 0, 1063, 1064, 1, 0, 0, 0, 1064, 257, 1, 0, 0, 0, 1065, 1066, 3, 180, 83, 0, 1066, 1067, 1, 0, 0, 0, 1067, 1068, 6, 122, 24, 0, 1068, 259, 1, 0, 0, 0, 1069, 1070, 3, 256, 121, 0, 1070, 1071, 1, 0, 0, 0, 1071, 1072, 6, 123, 25, 0, 1072, 261, 1, 0, 0, 0, 1073, 1074, 3, 54, 20, 0, 1074, 1075, 1, 0, 0, 0, 1075, 1076, 6, 124, 10, 0, 1076, 263, 1, 0, 0, 0, 1077, 1078, 3, 56, 21, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1080, 6, 125, 10, 0, 1080, 265, 1, 0, 0, 0, 1081, 1082, 3, 58, 22, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1084, 6, 126, 10, 0, 1084, 267, 1, 0, 0, 0, 1085, 1086, 3, 74, 30, 0, 1086, 1087, 1, 0, 0, 0, 1087, 1088, 6, 127, 13, 0, 1088, 1089, 6, 127, 14, 0, 1089, 1090, 6, 127, 14, 0, 1090, 269, 1, 0, 0, 0, 1091, 1092, 3, 108, 47, 0, 1092, 1093, 1, 0, 0, 0, 1093, 1094, 6, 128, 17, 0, 1094, 271, 1, 0, 0, 0, 1095, 1096, 3, 112, 49, 0, 1096, 1097, 1, 0, 0, 0, 1097, 1098, 6, 129, 16, 0, 1098, 273, 1, 0, 0, 0, 1099, 1100, 3, 116, 51, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 6, 130, 20, 0, 1102, 275, 1, 0, 0, 0, 1103, 1104, 3, 252, 119, 0, 1104, 1105, 1, 0, 0, 0, 1105, 1106, 6, 131, 26, 0, 1106, 277, 1, 0, 0, 0, 1107, 1108, 3, 220, 103, 0, 1108, 1109, 1, 0, 0, 0, 1109, 1110, 6, 132, 21, 0, 1110, 279, 1, 0, 0, 0, 1111, 1112, 3, 180, 83, 0, 1112, 1113, 1, 0, 0, 0, 1113, 1114, 6, 133, 24, 0, 1114, 281, 1, 0, 0, 0, 1115, 1116, 3, 54, 20, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1118, 6, 134, 10, 0, 1118, 283, 1, 0, 0, 0, 1119, 1120, 3, 56, 21, 0, 1120, 1121, 1, 0, 0, 0, 1121, 1122, 6, 135, 10, 0, 1122, 285, 1, 0, 0, 0, 1123, 1124, 3, 58, 22, 0, 1124, 1125, 1, 0, 0, 0, 1125, 1126, 6, 136, 10, 0, 1126, 287, 1, 0, 0, 0, 1127, 1128, 3, 74, 30, 0, 1128, 1129, 1, 0, 0, 0, 1129, 1130, 6, 137, 13, 0, 1130, 1131, 6, 137, 14, 0, 1131, 289, 1, 0, 0, 0, 1132, 1133, 3, 116, 51, 0, 1133, 1134, 1, 0, 0, 0, 1134, 1135, 6, 138, 20, 0, 1135, 291, 1, 0, 0, 0, 1136, 1137, 3, 180, 83, 0, 1137, 1138, 1, 0, 0, 0, 1138, 1139, 6, 139, 24, 0, 1139, 293, 1, 0, 0, 0, 1140, 1141, 3, 176, 81, 0, 1141, 1142, 1, 0, 0, 0, 1142, 1143, 6, 140, 27, 0, 1143, 295, 1, 0, 0, 0, 1144, 1145, 3, 54, 20, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1147, 6, 141, 10, 0, 1147, 297, 1, 0, 0, 0, 1148, 1149, 3, 56, 21, 0, 1149, 1150, 1, 0, 0, 0, 1150, 1151, 6, 142, 10, 0, 1151, 299, 1, 0, 0, 0, 1152, 1153, 3, 58, 22, 0, 1153, 1154, 1, 0, 0, 0, 1154, 1155, 6, 143, 10, 0, 1155, 301, 1, 0, 0, 0, 1156, 1157, 3, 74, 30, 0, 1157, 1158, 1, 0, 0, 0, 1158, 1159, 6, 144, 13, 0, 1159, 1160, 6, 144, 14, 0, 1160, 303, 1, 0, 0, 0, 1161, 1162, 5, 105, 0, 0, 1162, 1163, 5, 110, 0, 0, 1163, 1164, 5, 102, 0, 0, 1164, 1165, 5, 111, 0, 0, 1165, 305, 1, 0, 0, 0, 1166, 1167, 3, 54, 20, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 6, 146, 10, 0, 1169, 307, 1, 0, 0, 0, 1170, 1171, 3, 56, 21, 0, 1171, 1172, 1, 0, 0, 0, 1172, 1173, 6, 147, 10, 0, 1173, 309, 1, 0, 0, 0, 1174, 1175, 3, 58, 22, 0, 1175, 1176, 1, 0, 0, 0, 1176, 1177, 6, 148, 10, 0, 1177, 311, 1, 0, 0, 0, 1178, 1179, 3, 74, 30, 0, 1179, 1180, 1, 0, 0, 0, 1180, 1181, 6, 149, 13, 0, 1181, 1182, 6, 149, 14, 0, 1182, 313, 1, 0, 0, 0, 1183, 1184, 5, 102, 0, 0, 1184, 1185, 5, 117, 0, 0, 1185, 1186, 5, 110, 0, 0, 1186, 1187, 5, 99, 0, 0, 1187, 1188, 5, 116, 0, 0, 1188, 1189, 5, 105, 0, 0, 1189, 1190, 5, 111, 0, 0, 1190, 1191, 5, 110, 0, 0, 1191, 1192, 5, 115, 0, 0, 1192, 315, 1, 0, 0, 0, 1193, 1194, 3, 54, 20, 0, 1194, 1195, 1, 0, 0, 0, 1195, 1196, 6, 151, 10, 0, 1196, 317, 1, 0, 0, 0, 1197, 1198, 3, 56, 21, 0, 1198, 1199, 1, 0, 0, 0, 1199, 1200, 6, 152, 10, 0, 1200, 319, 1, 0, 0, 0, 1201, 1202, 3, 58, 22, 0, 1202, 1203, 1, 0, 0, 0, 1203, 1204, 6, 153, 10, 0, 1204, 321, 1, 0, 0, 0, 1205, 1206, 3, 174, 80, 0, 1206, 1207, 1, 0, 0, 0, 1207, 1208, 6, 154, 15, 0, 1208, 1209, 6, 154, 14, 0, 1209, 323, 1, 0, 0, 0, 1210, 1211, 5, 58, 0, 0, 1211, 325, 1, 0, 0, 0, 1212, 1218, 3, 86, 36, 0, 1213, 1218, 3, 76, 31, 0, 1214, 1218, 3, 116, 51, 0, 1215, 1218, 3, 78, 32, 0, 1216, 1218, 3, 92, 39, 0, 1217, 1212, 1, 0, 0, 0, 1217, 1213, 1, 0, 0, 0, 1217, 1214, 1, 0, 0, 0, 1217, 1215, 1, 0, 0, 0, 1217, 1216, 1, 0, 0, 0, 1218, 1219, 1, 0, 0, 0, 1219, 1217, 1, 0, 0, 0, 1219, 1220, 1, 0, 0, 0, 1220, 327, 1, 0, 0, 0, 1221, 1222, 3, 54, 20, 0, 1222, 1223, 1, 0, 0, 0, 1223, 1224, 6, 157, 10, 0, 1224, 329, 1, 0, 0, 0, 1225, 1226, 3, 56, 21, 0, 1226, 1227, 1, 0, 0, 0, 1227, 1228, 6, 158, 10, 0, 1228, 331, 1, 0, 0, 0, 1229, 1230, 3, 58, 22, 0, 1230, 1231, 1, 0, 0, 0, 1231, 1232, 6, 159, 10, 0, 1232, 333, 1, 0, 0, 0, 1233, 1234, 3, 74, 30, 0, 1234, 1235, 1, 0, 0, 0, 1235, 1236, 6, 160, 13, 0, 1236, 1237, 6, 160, 14, 0, 1237, 335, 1, 0, 0, 0, 1238, 1239, 3, 62, 24, 0, 1239, 1240, 1, 0, 0, 0, 1240, 1241, 6, 161, 19, 0, 1241, 1242, 6, 161, 14, 0, 1242, 1243, 6, 161, 28, 0, 1243, 337, 1, 0, 0, 0, 1244, 1245, 3, 54, 20, 0, 1245, 1246, 1, 0, 0, 0, 1246, 1247, 6, 162, 10, 0, 1247, 339, 1, 0, 0, 0, 1248, 1249, 3, 56, 21, 0, 1249, 1250, 1, 0, 0, 0, 1250, 1251, 6, 163, 10, 0, 1251, 341, 1, 0, 0, 0, 1252, 1253, 3, 58, 22, 0, 1253, 1254, 1, 0, 0, 0, 1254, 1255, 6, 164, 10, 0, 1255, 343, 1, 0, 0, 0, 1256, 1257, 3, 112, 49, 0, 1257, 1258, 1, 0, 0, 0, 1258, 1259, 6, 165, 16, 0, 1259, 1260, 6, 165, 14, 0, 1260, 1261, 6, 165, 6, 0, 1261, 345, 1, 0, 0, 0, 1262, 1263, 3, 54, 20, 0, 1263, 1264, 1, 0, 0, 0, 1264, 1265, 6, 166, 10, 0, 1265, 347, 1, 0, 0, 0, 1266, 1267, 3, 56, 21, 0, 1267, 1268, 1, 0, 0, 0, 1268, 1269, 6, 167, 10, 0, 1269, 349, 1, 0, 0, 0, 1270, 1271, 3, 58, 22, 0, 1271, 1272, 1, 0, 0, 0, 1272, 1273, 6, 168, 10, 0, 1273, 351, 1, 0, 0, 0, 1274, 1275, 3, 180, 83, 0, 1275, 1276, 1, 0, 0, 0, 1276, 1277, 6, 169, 14, 0, 1277, 1278, 6, 169, 0, 0, 1278, 1279, 6, 169, 24, 0, 1279, 353, 1, 0, 0, 0, 1280, 1281, 3, 176, 81, 0, 1281, 1282, 1, 0, 0, 0, 1282, 1283, 6, 170, 14, 0, 1283, 1284, 6, 170, 0, 0, 1284, 1285, 6, 170, 27, 0, 1285, 355, 1, 0, 0, 0, 1286, 1287, 3, 102, 44, 0, 1287, 1288, 1, 0, 0, 0, 1288, 1289, 6, 171, 14, 0, 1289, 1290, 6, 171, 0, 0, 1290, 1291, 6, 171, 29, 0, 1291, 357, 1, 0, 0, 0, 1292, 1293, 3, 74, 30, 0, 1293, 1294, 1, 0, 0, 0, 1294, 1295, 6, 172, 13, 0, 1295, 1296, 6, 172, 14, 0, 1296, 359, 1, 0, 0, 0, 60, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 523, 533, 537, 540, 549, 551, 562, 569, 574, 613, 618, 627, 634, 639, 641, 652, 660, 663, 665, 670, 675, 681, 688, 693, 699, 702, 710, 714, 847, 852, 857, 859, 865, 950, 954, 959, 964, 969, 971, 975, 977, 1054, 1058, 1063, 1217, 1219, 30, 5, 2, 0, 5, 4, 0, 5, 6, 0, 5, 1, 0, 5, 3, 0, 5, 10, 0, 5, 12, 0, 5, 8, 0, 5, 5, 0, 5, 9, 0, 0, 1, 0, 7, 67, 0, 5, 0, 0, 7, 28, 0, 4, 0, 0, 7, 68, 0, 7, 37, 0, 7, 35, 0, 7, 29, 0, 7, 24, 0, 7, 39, 0, 7, 78, 0, 5, 11, 0, 5, 7, 0, 7, 70, 0, 7, 88, 0, 7, 87, 0, 7, 69, 0, 5, 13, 0, 7, 32, 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 d7a73eeb844d0..3f769b2614479 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 @@ -28,18 +28,18 @@ public class EsqlBaseLexer extends Lexer { RP=53, TRUE=54, EQ=55, CIEQ=56, NEQ=57, LT=58, LTE=59, GT=60, GTE=61, PLUS=62, MINUS=63, ASTERISK=64, SLASH=65, PERCENT=66, OPENING_BRACKET=67, CLOSING_BRACKET=68, UNQUOTED_IDENTIFIER=69, QUOTED_IDENTIFIER=70, EXPR_LINE_COMMENT=71, - EXPR_MULTILINE_COMMENT=72, EXPR_WS=73, OPTIONS=74, METADATA=75, FROM_LINE_COMMENT=76, - FROM_MULTILINE_COMMENT=77, FROM_WS=78, ID_PATTERN=79, PROJECT_LINE_COMMENT=80, - PROJECT_MULTILINE_COMMENT=81, PROJECT_WS=82, AS=83, RENAME_LINE_COMMENT=84, - RENAME_MULTILINE_COMMENT=85, RENAME_WS=86, ON=87, WITH=88, ENRICH_POLICY_NAME=89, - ENRICH_LINE_COMMENT=90, ENRICH_MULTILINE_COMMENT=91, ENRICH_WS=92, ENRICH_FIELD_LINE_COMMENT=93, - ENRICH_FIELD_MULTILINE_COMMENT=94, ENRICH_FIELD_WS=95, MVEXPAND_LINE_COMMENT=96, - MVEXPAND_MULTILINE_COMMENT=97, MVEXPAND_WS=98, INFO=99, SHOW_LINE_COMMENT=100, - SHOW_MULTILINE_COMMENT=101, SHOW_WS=102, FUNCTIONS=103, META_LINE_COMMENT=104, - META_MULTILINE_COMMENT=105, META_WS=106, COLON=107, SETTING=108, SETTING_LINE_COMMENT=109, - SETTTING_MULTILINE_COMMENT=110, SETTING_WS=111, METRICS_LINE_COMMENT=112, - METRICS_MULTILINE_COMMENT=113, METRICS_WS=114, CLOSING_METRICS_LINE_COMMENT=115, - CLOSING_METRICS_MULTILINE_COMMENT=116, CLOSING_METRICS_WS=117; + EXPR_MULTILINE_COMMENT=72, EXPR_WS=73, METADATA=74, FROM_LINE_COMMENT=75, + FROM_MULTILINE_COMMENT=76, FROM_WS=77, ID_PATTERN=78, PROJECT_LINE_COMMENT=79, + PROJECT_MULTILINE_COMMENT=80, PROJECT_WS=81, AS=82, RENAME_LINE_COMMENT=83, + RENAME_MULTILINE_COMMENT=84, RENAME_WS=85, ON=86, WITH=87, ENRICH_POLICY_NAME=88, + ENRICH_LINE_COMMENT=89, ENRICH_MULTILINE_COMMENT=90, ENRICH_WS=91, ENRICH_FIELD_LINE_COMMENT=92, + ENRICH_FIELD_MULTILINE_COMMENT=93, ENRICH_FIELD_WS=94, MVEXPAND_LINE_COMMENT=95, + MVEXPAND_MULTILINE_COMMENT=96, MVEXPAND_WS=97, INFO=98, SHOW_LINE_COMMENT=99, + SHOW_MULTILINE_COMMENT=100, SHOW_WS=101, FUNCTIONS=102, META_LINE_COMMENT=103, + META_MULTILINE_COMMENT=104, META_WS=105, COLON=106, SETTING=107, SETTING_LINE_COMMENT=108, + SETTTING_MULTILINE_COMMENT=109, SETTING_WS=110, METRICS_LINE_COMMENT=111, + METRICS_MULTILINE_COMMENT=112, METRICS_WS=113, CLOSING_METRICS_LINE_COMMENT=114, + CLOSING_METRICS_MULTILINE_COMMENT=115, CLOSING_METRICS_WS=116; public static final int EXPLAIN_MODE=1, EXPRESSION_MODE=2, FROM_MODE=3, PROJECT_MODE=4, RENAME_MODE=5, ENRICH_MODE=6, ENRICH_FIELD_MODE=7, MVEXPAND_MODE=8, SHOW_MODE=9, META_MODE=10, @@ -71,22 +71,21 @@ private static String[] makeRuleNames() { "PERCENT", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "FROM_PIPE", "FROM_OPENING_BRACKET", "FROM_CLOSING_BRACKET", - "FROM_COMMA", "FROM_ASSIGN", "FROM_QUOTED_STRING", "OPTIONS", "METADATA", - "FROM_INDEX_UNQUOTED_IDENTIFIER", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", - "FROM_WS", "PROJECT_PIPE", "PROJECT_DOT", "PROJECT_COMMA", "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", - "AS", "RENAME_ID_PATTERN", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", - "RENAME_WS", "ENRICH_PIPE", "ENRICH_OPENING_BRACKET", "ON", "WITH", "ENRICH_POLICY_NAME_BODY", - "ENRICH_POLICY_NAME", "ENRICH_QUOTED_IDENTIFIER", "ENRICH_MODE_UNQUOTED_VALUE", - "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_PIPE", - "ENRICH_FIELD_ASSIGN", "ENRICH_FIELD_COMMA", "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", - "ENRICH_FIELD_ID_PATTERN", "ENRICH_FIELD_QUOTED_IDENTIFIER", "ENRICH_FIELD_LINE_COMMENT", - "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "MVEXPAND_PIPE", - "MVEXPAND_DOT", "MVEXPAND_QUOTED_IDENTIFIER", "MVEXPAND_UNQUOTED_IDENTIFIER", - "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", - "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS", - "META_PIPE", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", + "FROM_COMMA", "FROM_ASSIGN", "FROM_QUOTED_STRING", "METADATA", "FROM_INDEX_UNQUOTED_IDENTIFIER", + "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", "FROM_WS", "PROJECT_PIPE", + "PROJECT_DOT", "PROJECT_COMMA", "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", "AS", "RENAME_ID_PATTERN", + "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", "ENRICH_PIPE", + "ENRICH_OPENING_BRACKET", "ON", "WITH", "ENRICH_POLICY_NAME_BODY", "ENRICH_POLICY_NAME", + "ENRICH_QUOTED_IDENTIFIER", "ENRICH_MODE_UNQUOTED_VALUE", "ENRICH_LINE_COMMENT", + "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_PIPE", "ENRICH_FIELD_ASSIGN", + "ENRICH_FIELD_COMMA", "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", "ENRICH_FIELD_ID_PATTERN", + "ENRICH_FIELD_QUOTED_IDENTIFIER", "ENRICH_FIELD_LINE_COMMENT", "ENRICH_FIELD_MULTILINE_COMMENT", + "ENRICH_FIELD_WS", "MVEXPAND_PIPE", "MVEXPAND_DOT", "MVEXPAND_QUOTED_IDENTIFIER", + "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", + "MVEXPAND_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", + "SHOW_WS", "META_PIPE", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", "META_WS", "SETTING_CLOSING_BRACKET", "COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "METRICS_PIPE", "METRICS_INDEX_UNQUOTED_IDENTIFIER", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", "CLOSING_METRICS_COMMA", @@ -107,10 +106,10 @@ private static String[] makeLiteralNames() { "'first'", "'last'", "'('", "'in'", "'is'", "'like'", "'not'", "'null'", "'nulls'", "'or'", "'?'", "'rlike'", "')'", "'true'", "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", - null, "']'", null, null, null, null, null, "'options'", "'metadata'", - null, null, null, null, null, null, null, "'as'", null, null, null, "'on'", - "'with'", null, null, null, null, null, null, null, null, null, null, - "'info'", null, null, null, "'functions'", null, null, null, "':'" + null, "']'", null, null, null, null, null, "'metadata'", null, null, + null, null, null, null, null, "'as'", null, null, null, "'on'", "'with'", + null, null, null, null, null, null, null, null, null, null, "'info'", + null, null, null, "'functions'", null, null, null, "':'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); @@ -127,17 +126,17 @@ private static String[] makeSymbolicNames() { "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", - "OPTIONS", "METADATA", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", - "FROM_WS", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", - "PROJECT_WS", "AS", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", - "RENAME_WS", "ON", "WITH", "ENRICH_POLICY_NAME", "ENRICH_LINE_COMMENT", - "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", - "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "MVEXPAND_LINE_COMMENT", - "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "INFO", "SHOW_LINE_COMMENT", - "SHOW_MULTILINE_COMMENT", "SHOW_WS", "FUNCTIONS", "META_LINE_COMMENT", - "META_MULTILINE_COMMENT", "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", - "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", - "METRICS_WS", "CLOSING_METRICS_LINE_COMMENT", "CLOSING_METRICS_MULTILINE_COMMENT", + "METADATA", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", "FROM_WS", + "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", + "AS", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", + "ON", "WITH", "ENRICH_POLICY_NAME", "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", + "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", "ENRICH_FIELD_MULTILINE_COMMENT", + "ENRICH_FIELD_WS", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", + "MVEXPAND_WS", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", + "SHOW_WS", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", + "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", + "SETTING_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", + "CLOSING_METRICS_LINE_COMMENT", "CLOSING_METRICS_MULTILINE_COMMENT", "CLOSING_METRICS_WS" }; } @@ -201,7 +200,7 @@ public EsqlBaseLexer(CharStream input) { public ATN getATN() { return _ATN; } public static final String _serializedATN = - "\u0004\u0000u\u051b\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ + "\u0004\u0000t\u0511\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ "\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ "\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff"+ "\u0006\uffff\uffff\u0006\uffff\uffff\u0006\uffff\uffff\u0002\u0000\u0007"+ @@ -249,784 +248,778 @@ public EsqlBaseLexer(CharStream input) { "\u00a3\u0007\u00a3\u0002\u00a4\u0007\u00a4\u0002\u00a5\u0007\u00a5\u0002"+ "\u00a6\u0007\u00a6\u0002\u00a7\u0007\u00a7\u0002\u00a8\u0007\u00a8\u0002"+ "\u00a9\u0007\u00a9\u0002\u00aa\u0007\u00aa\u0002\u00ab\u0007\u00ab\u0002"+ - "\u00ac\u0007\u00ac\u0002\u00ad\u0007\u00ad\u0001\u0000\u0001\u0000\u0001"+ + "\u00ac\u0007\u00ac\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+ "\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001\u0000\u0001"+ - "\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ - "\u0001\u0001\u0001\u0001\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+ - "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+ - "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+ - "\u0003\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001"+ - "\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001"+ - "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+ - "\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001"+ - "\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+ + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ + "\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+ + "\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0003\u0001\u0003\u0001"+ + "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0004\u0001"+ + "\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001\u0004\u0001"+ + "\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+ + "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0006\u0001\u0006\u0001"+ + "\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0007\u0001"+ + "\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+ "\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\t\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\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\t\u0001\n\u0001"+ + "\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\u000b\u0001\u000b\u0001"+ "\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001"+ - "\u000b\u0001\u000b\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0001\f\u0001"+ - "\f\u0001\f\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\r\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\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\u0012\u0001\u0012\u0001"+ - "\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+ - "\u0013\u0004\u0013\u020c\b\u0013\u000b\u0013\f\u0013\u020d\u0001\u0013"+ - "\u0001\u0013\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0005\u0014"+ - "\u0216\b\u0014\n\u0014\f\u0014\u0219\t\u0014\u0001\u0014\u0003\u0014\u021c"+ - "\b\u0014\u0001\u0014\u0003\u0014\u021f\b\u0014\u0001\u0014\u0001\u0014"+ - "\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0005\u0015"+ - "\u0228\b\u0015\n\u0015\f\u0015\u022b\t\u0015\u0001\u0015\u0001\u0015\u0001"+ - "\u0015\u0001\u0015\u0001\u0015\u0001\u0016\u0004\u0016\u0233\b\u0016\u000b"+ - "\u0016\f\u0016\u0234\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017\u0001"+ - "\u0017\u0003\u0017\u023c\b\u0017\u0001\u0018\u0004\u0018\u023f\b\u0018"+ - "\u000b\u0018\f\u0018\u0240\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019"+ - "\u0001\u0019\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a"+ - "\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c"+ - "\u0001\u001c\u0001\u001c\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d"+ - "\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001f\u0001\u001f"+ - "\u0001 \u0001 \u0001!\u0001!\u0001!\u0001\"\u0001\"\u0001#\u0001#\u0003"+ - "#\u0268\b#\u0001#\u0004#\u026b\b#\u000b#\f#\u026c\u0001$\u0001$\u0001"+ - "%\u0001%\u0001&\u0001&\u0001&\u0003&\u0276\b&\u0001\'\u0001\'\u0001(\u0001"+ - "(\u0001(\u0003(\u027d\b(\u0001)\u0001)\u0001)\u0005)\u0282\b)\n)\f)\u0285"+ - "\t)\u0001)\u0001)\u0001)\u0001)\u0001)\u0001)\u0005)\u028d\b)\n)\f)\u0290"+ - "\t)\u0001)\u0001)\u0001)\u0001)\u0001)\u0003)\u0297\b)\u0001)\u0003)\u029a"+ - "\b)\u0003)\u029c\b)\u0001*\u0004*\u029f\b*\u000b*\f*\u02a0\u0001+\u0004"+ - "+\u02a4\b+\u000b+\f+\u02a5\u0001+\u0001+\u0005+\u02aa\b+\n+\f+\u02ad\t"+ - "+\u0001+\u0001+\u0004+\u02b1\b+\u000b+\f+\u02b2\u0001+\u0004+\u02b6\b"+ - "+\u000b+\f+\u02b7\u0001+\u0001+\u0005+\u02bc\b+\n+\f+\u02bf\t+\u0003+"+ - "\u02c1\b+\u0001+\u0001+\u0001+\u0001+\u0004+\u02c7\b+\u000b+\f+\u02c8"+ - "\u0001+\u0001+\u0003+\u02cd\b+\u0001,\u0001,\u0001,\u0001-\u0001-\u0001"+ - "-\u0001-\u0001.\u0001.\u0001.\u0001.\u0001/\u0001/\u00010\u00010\u0001"+ - "0\u00011\u00011\u00012\u00012\u00012\u00012\u00012\u00013\u00013\u0001"+ - "4\u00014\u00014\u00014\u00014\u00014\u00015\u00015\u00015\u00015\u0001"+ - "5\u00015\u00016\u00016\u00016\u00016\u00016\u00017\u00017\u00018\u0001"+ - "8\u00018\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@\u0001@\u0001@\u0001@\u0001A\u0001A\u0001B\u0001B\u0001"+ - "B\u0001B\u0001B\u0001C\u0001C\u0001C\u0001D\u0001D\u0001D\u0001E\u0001"+ - "E\u0001E\u0001F\u0001F\u0001G\u0001G\u0001G\u0001H\u0001H\u0001I\u0001"+ - "I\u0001I\u0001J\u0001J\u0001K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001"+ - "N\u0001N\u0001O\u0001O\u0001O\u0001O\u0001O\u0001P\u0001P\u0001P\u0001"+ - "P\u0001P\u0001Q\u0001Q\u0005Q\u0350\bQ\nQ\fQ\u0353\tQ\u0001Q\u0001Q\u0003"+ - "Q\u0357\bQ\u0001Q\u0004Q\u035a\bQ\u000bQ\fQ\u035b\u0003Q\u035e\bQ\u0001"+ - "R\u0001R\u0004R\u0362\bR\u000bR\fR\u0363\u0001R\u0001R\u0001S\u0001S\u0001"+ - "T\u0001T\u0001T\u0001T\u0001U\u0001U\u0001U\u0001U\u0001V\u0001V\u0001"+ - "V\u0001V\u0001W\u0001W\u0001W\u0001W\u0001W\u0001X\u0001X\u0001X\u0001"+ - "X\u0001Y\u0001Y\u0001Y\u0001Y\u0001Z\u0001Z\u0001Z\u0001Z\u0001[\u0001"+ - "[\u0001[\u0001[\u0001\\\u0001\\\u0001\\\u0001\\\u0001]\u0001]\u0001]\u0001"+ - "]\u0001]\u0001]\u0001]\u0001]\u0001^\u0001^\u0001^\u0001^\u0001^\u0001"+ - "^\u0001^\u0001^\u0001^\u0001_\u0001_\u0001_\u0001_\u0001`\u0001`\u0001"+ - "`\u0001`\u0001a\u0001a\u0001a\u0001a\u0001b\u0001b\u0001b\u0001b\u0001"+ - "c\u0001c\u0001c\u0001c\u0001c\u0001d\u0001d\u0001d\u0001d\u0001e\u0001"+ - "e\u0001e\u0001e\u0001f\u0001f\u0001f\u0001f\u0003f\u03c1\bf\u0001g\u0001"+ - "g\u0003g\u03c5\bg\u0001g\u0005g\u03c8\bg\ng\fg\u03cb\tg\u0001g\u0001g"+ - "\u0003g\u03cf\bg\u0001g\u0004g\u03d2\bg\u000bg\fg\u03d3\u0003g\u03d6\b"+ - "g\u0001h\u0001h\u0004h\u03da\bh\u000bh\fh\u03db\u0001i\u0001i\u0001i\u0001"+ - "i\u0001j\u0001j\u0001j\u0001j\u0001k\u0001k\u0001k\u0001k\u0001l\u0001"+ - "l\u0001l\u0001l\u0001l\u0001m\u0001m\u0001m\u0001m\u0001n\u0001n\u0001"+ - "n\u0001n\u0001o\u0001o\u0001o\u0001o\u0001p\u0001p\u0001p\u0001q\u0001"+ - "q\u0001q\u0001q\u0001r\u0001r\u0001r\u0001r\u0001s\u0001s\u0001s\u0001"+ - "s\u0001t\u0001t\u0001t\u0001t\u0001u\u0001u\u0001u\u0001u\u0001u\u0001"+ - "v\u0001v\u0001v\u0001v\u0001v\u0001w\u0001w\u0001w\u0001w\u0001w\u0001"+ - "x\u0001x\u0001x\u0001x\u0001x\u0001x\u0001x\u0001y\u0001y\u0001z\u0004"+ - "z\u0427\bz\u000bz\fz\u0428\u0001z\u0001z\u0003z\u042d\bz\u0001z\u0004"+ - "z\u0430\bz\u000bz\fz\u0431\u0001{\u0001{\u0001{\u0001{\u0001|\u0001|\u0001"+ - "|\u0001|\u0001}\u0001}\u0001}\u0001}\u0001~\u0001~\u0001~\u0001~\u0001"+ - "\u007f\u0001\u007f\u0001\u007f\u0001\u007f\u0001\u0080\u0001\u0080\u0001"+ - "\u0080\u0001\u0080\u0001\u0080\u0001\u0080\u0001\u0081\u0001\u0081\u0001"+ - "\u0081\u0001\u0081\u0001\u0082\u0001\u0082\u0001\u0082\u0001\u0082\u0001"+ - "\u0083\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0084\u0001\u0084\u0001"+ - "\u0084\u0001\u0084\u0001\u0085\u0001\u0085\u0001\u0085\u0001\u0085\u0001"+ - "\u0086\u0001\u0086\u0001\u0086\u0001\u0086\u0001\u0087\u0001\u0087\u0001"+ - "\u0087\u0001\u0087\u0001\u0088\u0001\u0088\u0001\u0088\u0001\u0088\u0001"+ - "\u0089\u0001\u0089\u0001\u0089\u0001\u0089\u0001\u008a\u0001\u008a\u0001"+ - "\u008a\u0001\u008a\u0001\u008a\u0001\u008b\u0001\u008b\u0001\u008b\u0001"+ - "\u008b\u0001\u008c\u0001\u008c\u0001\u008c\u0001\u008c\u0001\u008d\u0001"+ - "\u008d\u0001\u008d\u0001\u008d\u0001\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"+ - "\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\u0096\u0001"+ - "\u0097\u0001\u0097\u0001\u0097\u0001\u0097\u0001\u0097\u0001\u0097\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"+ - "\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0001\u009b\u0001\u009b\u0001"+ - "\u009b\u0001\u009b\u0001\u009b\u0001\u009c\u0001\u009c\u0001\u009d\u0001"+ - "\u009d\u0001\u009d\u0001\u009d\u0001\u009d\u0004\u009d\u04cc\b\u009d\u000b"+ - "\u009d\f\u009d\u04cd\u0001\u009e\u0001\u009e\u0001\u009e\u0001\u009e\u0001"+ - "\u009f\u0001\u009f\u0001\u009f\u0001\u009f\u0001\u00a0\u0001\u00a0\u0001"+ - "\u00a0\u0001\u00a0\u0001\u00a1\u0001\u00a1\u0001\u00a1\u0001\u00a1\u0001"+ - "\u00a1\u0001\u00a2\u0001\u00a2\u0001\u00a2\u0001\u00a2\u0001\u00a2\u0001"+ - "\u00a2\u0001\u00a3\u0001\u00a3\u0001\u00a3\u0001\u00a3\u0001\u00a4\u0001"+ - "\u00a4\u0001\u00a4\u0001\u00a4\u0001\u00a5\u0001\u00a5\u0001\u00a5\u0001"+ - "\u00a5\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001"+ - "\u00a6\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\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001"+ - "\u00aa\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001"+ - "\u00ab\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0001"+ - "\u00ac\u0001\u00ad\u0001\u00ad\u0001\u00ad\u0001\u00ad\u0001\u00ad\u0002"+ - "\u0229\u028e\u0000\u00ae\u000e\u0001\u0010\u0002\u0012\u0003\u0014\u0004"+ - "\u0016\u0005\u0018\u0006\u001a\u0007\u001c\b\u001e\t \n\"\u000b$\f&\r"+ - "(\u000e*\u000f,\u0010.\u00110\u00122\u00134\u00146\u00158\u0016:\u0017"+ - "<\u0000>\u0018@\u0000B\u0000D\u0019F\u001aH\u001bJ\u001cL\u0000N\u0000"+ - "P\u0000R\u0000T\u0000V\u0000X\u0000Z\u0000\\\u0000^\u0000`\u001db\u001e"+ - "d\u001ff h!j\"l#n$p%r&t\'v(x)z*|+~,\u0080-\u0082.\u0084/\u00860\u0088"+ - "1\u008a2\u008c3\u008e4\u00905\u00926\u00947\u00968\u00989\u009a:\u009c"+ - ";\u009e<\u00a0=\u00a2>\u00a4?\u00a6@\u00a8A\u00aaB\u00acC\u00aeD\u00b0"+ - "E\u00b2\u0000\u00b4F\u00b6G\u00b8H\u00baI\u00bc\u0000\u00be\u0000\u00c0"+ - "\u0000\u00c2\u0000\u00c4\u0000\u00c6\u0000\u00c8J\u00caK\u00cc\u0000\u00ce"+ - "L\u00d0M\u00d2N\u00d4\u0000\u00d6\u0000\u00d8\u0000\u00da\u0000\u00dc"+ - "\u0000\u00deO\u00e0P\u00e2Q\u00e4R\u00e6\u0000\u00e8\u0000\u00ea\u0000"+ - "\u00ec\u0000\u00eeS\u00f0\u0000\u00f2T\u00f4U\u00f6V\u00f8\u0000\u00fa"+ - "\u0000\u00fcW\u00feX\u0100\u0000\u0102Y\u0104\u0000\u0106\u0000\u0108"+ - "Z\u010a[\u010c\\\u010e\u0000\u0110\u0000\u0112\u0000\u0114\u0000\u0116"+ - "\u0000\u0118\u0000\u011a\u0000\u011c]\u011e^\u0120_\u0122\u0000\u0124"+ - "\u0000\u0126\u0000\u0128\u0000\u012a`\u012ca\u012eb\u0130\u0000\u0132"+ - "c\u0134d\u0136e\u0138f\u013a\u0000\u013cg\u013eh\u0140i\u0142j\u0144\u0000"+ - "\u0146k\u0148l\u014am\u014cn\u014eo\u0150\u0000\u0152\u0000\u0154p\u0156"+ - "q\u0158r\u015a\u0000\u015cs\u015et\u0160u\u0162\u0000\u0164\u0000\u0166"+ - "\u0000\u0168\u0000\u000e\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007"+ - "\b\t\n\u000b\f\r\r\u0006\u0000\t\n\r\r //[[]]\u0002\u0000\n\n\r\r\u0003"+ - "\u0000\t\n\r\r \n\u0000\t\n\r\r ,,//==[[]]``||\u0002\u0000**//\u0001"+ - "\u000009\u0002\u0000AZaz\u0005\u0000\"\"\\\\nnrrtt\u0004\u0000\n\n\r\r"+ - "\"\"\\\\\u0002\u0000EEee\u0002\u0000++--\u0001\u0000``\u000b\u0000\t\n"+ - "\r\r \"#,,//::<<>?\\\\||\u0534\u0000\u000e\u0001\u0000\u0000\u0000\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\u0001@\u0001\u0000\u0000\u0000\u0001B\u0001"+ - "\u0000\u0000\u0000\u0001D\u0001\u0000\u0000\u0000\u0001F\u0001\u0000\u0000"+ - "\u0000\u0001H\u0001\u0000\u0000\u0000\u0002J\u0001\u0000\u0000\u0000\u0002"+ - "`\u0001\u0000\u0000\u0000\u0002b\u0001\u0000\u0000\u0000\u0002d\u0001"+ - "\u0000\u0000\u0000\u0002f\u0001\u0000\u0000\u0000\u0002h\u0001\u0000\u0000"+ - "\u0000\u0002j\u0001\u0000\u0000\u0000\u0002l\u0001\u0000\u0000\u0000\u0002"+ - "n\u0001\u0000\u0000\u0000\u0002p\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\u0002"+ - "|\u0001\u0000\u0000\u0000\u0002~\u0001\u0000\u0000\u0000\u0002\u0080\u0001"+ - "\u0000\u0000\u0000\u0002\u0082\u0001\u0000\u0000\u0000\u0002\u0084\u0001"+ - "\u0000\u0000\u0000\u0002\u0086\u0001\u0000\u0000\u0000\u0002\u0088\u0001"+ - "\u0000\u0000\u0000\u0002\u008a\u0001\u0000\u0000\u0000\u0002\u008c\u0001"+ - "\u0000\u0000\u0000\u0002\u008e\u0001\u0000\u0000\u0000\u0002\u0090\u0001"+ - "\u0000\u0000\u0000\u0002\u0092\u0001\u0000\u0000\u0000\u0002\u0094\u0001"+ - "\u0000\u0000\u0000\u0002\u0096\u0001\u0000\u0000\u0000\u0002\u0098\u0001"+ - "\u0000\u0000\u0000\u0002\u009a\u0001\u0000\u0000\u0000\u0002\u009c\u0001"+ - "\u0000\u0000\u0000\u0002\u009e\u0001\u0000\u0000\u0000\u0002\u00a0\u0001"+ - "\u0000\u0000\u0000\u0002\u00a2\u0001\u0000\u0000\u0000\u0002\u00a4\u0001"+ - "\u0000\u0000\u0000\u0002\u00a6\u0001\u0000\u0000\u0000\u0002\u00a8\u0001"+ - "\u0000\u0000\u0000\u0002\u00aa\u0001\u0000\u0000\u0000\u0002\u00ac\u0001"+ - "\u0000\u0000\u0000\u0002\u00ae\u0001\u0000\u0000\u0000\u0002\u00b0\u0001"+ - "\u0000\u0000\u0000\u0002\u00b4\u0001\u0000\u0000\u0000\u0002\u00b6\u0001"+ - "\u0000\u0000\u0000\u0002\u00b8\u0001\u0000\u0000\u0000\u0002\u00ba\u0001"+ - "\u0000\u0000\u0000\u0003\u00bc\u0001\u0000\u0000\u0000\u0003\u00be\u0001"+ - "\u0000\u0000\u0000\u0003\u00c0\u0001\u0000\u0000\u0000\u0003\u00c2\u0001"+ - "\u0000\u0000\u0000\u0003\u00c4\u0001\u0000\u0000\u0000\u0003\u00c6\u0001"+ - "\u0000\u0000\u0000\u0003\u00c8\u0001\u0000\u0000\u0000\u0003\u00ca\u0001"+ - "\u0000\u0000\u0000\u0003\u00cc\u0001\u0000\u0000\u0000\u0003\u00ce\u0001"+ - "\u0000\u0000\u0000\u0003\u00d0\u0001\u0000\u0000\u0000\u0003\u00d2\u0001"+ - "\u0000\u0000\u0000\u0004\u00d4\u0001\u0000\u0000\u0000\u0004\u00d6\u0001"+ - "\u0000\u0000\u0000\u0004\u00d8\u0001\u0000\u0000\u0000\u0004\u00de\u0001"+ - "\u0000\u0000\u0000\u0004\u00e0\u0001\u0000\u0000\u0000\u0004\u00e2\u0001"+ - "\u0000\u0000\u0000\u0004\u00e4\u0001\u0000\u0000\u0000\u0005\u00e6\u0001"+ - "\u0000\u0000\u0000\u0005\u00e8\u0001\u0000\u0000\u0000\u0005\u00ea\u0001"+ - "\u0000\u0000\u0000\u0005\u00ec\u0001\u0000\u0000\u0000\u0005\u00ee\u0001"+ - "\u0000\u0000\u0000\u0005\u00f0\u0001\u0000\u0000\u0000\u0005\u00f2\u0001"+ - "\u0000\u0000\u0000\u0005\u00f4\u0001\u0000\u0000\u0000\u0005\u00f6\u0001"+ - "\u0000\u0000\u0000\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\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\u0007\u010e\u0001\u0000\u0000\u0000\u0007\u0110\u0001"+ - "\u0000\u0000\u0000\u0007\u0112\u0001\u0000\u0000\u0000\u0007\u0114\u0001"+ - "\u0000\u0000\u0000\u0007\u0116\u0001\u0000\u0000\u0000\u0007\u0118\u0001"+ - "\u0000\u0000\u0000\u0007\u011a\u0001\u0000\u0000\u0000\u0007\u011c\u0001"+ - "\u0000\u0000\u0000\u0007\u011e\u0001\u0000\u0000\u0000\u0007\u0120\u0001"+ - "\u0000\u0000\u0000\b\u0122\u0001\u0000\u0000\u0000\b\u0124\u0001\u0000"+ - "\u0000\u0000\b\u0126\u0001\u0000\u0000\u0000\b\u0128\u0001\u0000\u0000"+ - "\u0000\b\u012a\u0001\u0000\u0000\u0000\b\u012c\u0001\u0000\u0000\u0000"+ - "\b\u012e\u0001\u0000\u0000\u0000\t\u0130\u0001\u0000\u0000\u0000\t\u0132"+ - "\u0001\u0000\u0000\u0000\t\u0134\u0001\u0000\u0000\u0000\t\u0136\u0001"+ - "\u0000\u0000\u0000\t\u0138\u0001\u0000\u0000\u0000\n\u013a\u0001\u0000"+ - "\u0000\u0000\n\u013c\u0001\u0000\u0000\u0000\n\u013e\u0001\u0000\u0000"+ - "\u0000\n\u0140\u0001\u0000\u0000\u0000\n\u0142\u0001\u0000\u0000\u0000"+ - "\u000b\u0144\u0001\u0000\u0000\u0000\u000b\u0146\u0001\u0000\u0000\u0000"+ - "\u000b\u0148\u0001\u0000\u0000\u0000\u000b\u014a\u0001\u0000\u0000\u0000"+ - "\u000b\u014c\u0001\u0000\u0000\u0000\u000b\u014e\u0001\u0000\u0000\u0000"+ - "\f\u0150\u0001\u0000\u0000\u0000\f\u0152\u0001\u0000\u0000\u0000\f\u0154"+ - "\u0001\u0000\u0000\u0000\f\u0156\u0001\u0000\u0000\u0000\f\u0158\u0001"+ - "\u0000\u0000\u0000\r\u015a\u0001\u0000\u0000\u0000\r\u015c\u0001\u0000"+ - "\u0000\u0000\r\u015e\u0001\u0000\u0000\u0000\r\u0160\u0001\u0000\u0000"+ - "\u0000\r\u0162\u0001\u0000\u0000\u0000\r\u0164\u0001\u0000\u0000\u0000"+ - "\r\u0166\u0001\u0000\u0000\u0000\r\u0168\u0001\u0000\u0000\u0000\u000e"+ - "\u016a\u0001\u0000\u0000\u0000\u0010\u0174\u0001\u0000\u0000\u0000\u0012"+ - "\u017b\u0001\u0000\u0000\u0000\u0014\u0184\u0001\u0000\u0000\u0000\u0016"+ - "\u018b\u0001\u0000\u0000\u0000\u0018\u0195\u0001\u0000\u0000\u0000\u001a"+ - "\u019c\u0001\u0000\u0000\u0000\u001c\u01a3\u0001\u0000\u0000\u0000\u001e"+ - "\u01b1\u0001\u0000\u0000\u0000 \u01b8\u0001\u0000\u0000\u0000\"\u01c0"+ - "\u0001\u0000\u0000\u0000$\u01c7\u0001\u0000\u0000\u0000&\u01d1\u0001\u0000"+ - "\u0000\u0000(\u01dd\u0001\u0000\u0000\u0000*\u01e6\u0001\u0000\u0000\u0000"+ - ",\u01ec\u0001\u0000\u0000\u0000.\u01f3\u0001\u0000\u0000\u00000\u01fa"+ - "\u0001\u0000\u0000\u00002\u0202\u0001\u0000\u0000\u00004\u020b\u0001\u0000"+ - "\u0000\u00006\u0211\u0001\u0000\u0000\u00008\u0222\u0001\u0000\u0000\u0000"+ - ":\u0232\u0001\u0000\u0000\u0000<\u023b\u0001\u0000\u0000\u0000>\u023e"+ - "\u0001\u0000\u0000\u0000@\u0242\u0001\u0000\u0000\u0000B\u0247\u0001\u0000"+ - "\u0000\u0000D\u024c\u0001\u0000\u0000\u0000F\u0250\u0001\u0000\u0000\u0000"+ - "H\u0254\u0001\u0000\u0000\u0000J\u0258\u0001\u0000\u0000\u0000L\u025c"+ - "\u0001\u0000\u0000\u0000N\u025e\u0001\u0000\u0000\u0000P\u0260\u0001\u0000"+ - "\u0000\u0000R\u0263\u0001\u0000\u0000\u0000T\u0265\u0001\u0000\u0000\u0000"+ - "V\u026e\u0001\u0000\u0000\u0000X\u0270\u0001\u0000\u0000\u0000Z\u0275"+ - "\u0001\u0000\u0000\u0000\\\u0277\u0001\u0000\u0000\u0000^\u027c\u0001"+ - "\u0000\u0000\u0000`\u029b\u0001\u0000\u0000\u0000b\u029e\u0001\u0000\u0000"+ - "\u0000d\u02cc\u0001\u0000\u0000\u0000f\u02ce\u0001\u0000\u0000\u0000h"+ - "\u02d1\u0001\u0000\u0000\u0000j\u02d5\u0001\u0000\u0000\u0000l\u02d9\u0001"+ - "\u0000\u0000\u0000n\u02db\u0001\u0000\u0000\u0000p\u02de\u0001\u0000\u0000"+ - "\u0000r\u02e0\u0001\u0000\u0000\u0000t\u02e5\u0001\u0000\u0000\u0000v"+ - "\u02e7\u0001\u0000\u0000\u0000x\u02ed\u0001\u0000\u0000\u0000z\u02f3\u0001"+ - "\u0000\u0000\u0000|\u02f8\u0001\u0000\u0000\u0000~\u02fa\u0001\u0000\u0000"+ - "\u0000\u0080\u02fd\u0001\u0000\u0000\u0000\u0082\u0300\u0001\u0000\u0000"+ - "\u0000\u0084\u0305\u0001\u0000\u0000\u0000\u0086\u0309\u0001\u0000\u0000"+ - "\u0000\u0088\u030e\u0001\u0000\u0000\u0000\u008a\u0314\u0001\u0000\u0000"+ - "\u0000\u008c\u0317\u0001\u0000\u0000\u0000\u008e\u0319\u0001\u0000\u0000"+ - "\u0000\u0090\u031f\u0001\u0000\u0000\u0000\u0092\u0321\u0001\u0000\u0000"+ - "\u0000\u0094\u0326\u0001\u0000\u0000\u0000\u0096\u0329\u0001\u0000\u0000"+ - "\u0000\u0098\u032c\u0001\u0000\u0000\u0000\u009a\u032f\u0001\u0000\u0000"+ - "\u0000\u009c\u0331\u0001\u0000\u0000\u0000\u009e\u0334\u0001\u0000\u0000"+ - "\u0000\u00a0\u0336\u0001\u0000\u0000\u0000\u00a2\u0339\u0001\u0000\u0000"+ - "\u0000\u00a4\u033b\u0001\u0000\u0000\u0000\u00a6\u033d\u0001\u0000\u0000"+ - "\u0000\u00a8\u033f\u0001\u0000\u0000\u0000\u00aa\u0341\u0001\u0000\u0000"+ - "\u0000\u00ac\u0343\u0001\u0000\u0000\u0000\u00ae\u0348\u0001\u0000\u0000"+ - "\u0000\u00b0\u035d\u0001\u0000\u0000\u0000\u00b2\u035f\u0001\u0000\u0000"+ - "\u0000\u00b4\u0367\u0001\u0000\u0000\u0000\u00b6\u0369\u0001\u0000\u0000"+ - "\u0000\u00b8\u036d\u0001\u0000\u0000\u0000\u00ba\u0371\u0001\u0000\u0000"+ - "\u0000\u00bc\u0375\u0001\u0000\u0000\u0000\u00be\u037a\u0001\u0000\u0000"+ - "\u0000\u00c0\u037e\u0001\u0000\u0000\u0000\u00c2\u0382\u0001\u0000\u0000"+ - "\u0000\u00c4\u0386\u0001\u0000\u0000\u0000\u00c6\u038a\u0001\u0000\u0000"+ - "\u0000\u00c8\u038e\u0001\u0000\u0000\u0000\u00ca\u0396\u0001\u0000\u0000"+ - "\u0000\u00cc\u039f\u0001\u0000\u0000\u0000\u00ce\u03a3\u0001\u0000\u0000"+ - "\u0000\u00d0\u03a7\u0001\u0000\u0000\u0000\u00d2\u03ab\u0001\u0000\u0000"+ - "\u0000\u00d4\u03af\u0001\u0000\u0000\u0000\u00d6\u03b4\u0001\u0000\u0000"+ - "\u0000\u00d8\u03b8\u0001\u0000\u0000\u0000\u00da\u03c0\u0001\u0000\u0000"+ - "\u0000\u00dc\u03d5\u0001\u0000\u0000\u0000\u00de\u03d9\u0001\u0000\u0000"+ - "\u0000\u00e0\u03dd\u0001\u0000\u0000\u0000\u00e2\u03e1\u0001\u0000\u0000"+ - "\u0000\u00e4\u03e5\u0001\u0000\u0000\u0000\u00e6\u03e9\u0001\u0000\u0000"+ - "\u0000\u00e8\u03ee\u0001\u0000\u0000\u0000\u00ea\u03f2\u0001\u0000\u0000"+ - "\u0000\u00ec\u03f6\u0001\u0000\u0000\u0000\u00ee\u03fa\u0001\u0000\u0000"+ - "\u0000\u00f0\u03fd\u0001\u0000\u0000\u0000\u00f2\u0401\u0001\u0000\u0000"+ - "\u0000\u00f4\u0405\u0001\u0000\u0000\u0000\u00f6\u0409\u0001\u0000\u0000"+ - "\u0000\u00f8\u040d\u0001\u0000\u0000\u0000\u00fa\u0412\u0001\u0000\u0000"+ - "\u0000\u00fc\u0417\u0001\u0000\u0000\u0000\u00fe\u041c\u0001\u0000\u0000"+ - "\u0000\u0100\u0423\u0001\u0000\u0000\u0000\u0102\u042c\u0001\u0000\u0000"+ - "\u0000\u0104\u0433\u0001\u0000\u0000\u0000\u0106\u0437\u0001\u0000\u0000"+ - "\u0000\u0108\u043b\u0001\u0000\u0000\u0000\u010a\u043f\u0001\u0000\u0000"+ - "\u0000\u010c\u0443\u0001\u0000\u0000\u0000\u010e\u0447\u0001\u0000\u0000"+ - "\u0000\u0110\u044d\u0001\u0000\u0000\u0000\u0112\u0451\u0001\u0000\u0000"+ - "\u0000\u0114\u0455\u0001\u0000\u0000\u0000\u0116\u0459\u0001\u0000\u0000"+ - "\u0000\u0118\u045d\u0001\u0000\u0000\u0000\u011a\u0461\u0001\u0000\u0000"+ - "\u0000\u011c\u0465\u0001\u0000\u0000\u0000\u011e\u0469\u0001\u0000\u0000"+ - "\u0000\u0120\u046d\u0001\u0000\u0000\u0000\u0122\u0471\u0001\u0000\u0000"+ - "\u0000\u0124\u0476\u0001\u0000\u0000\u0000\u0126\u047a\u0001\u0000\u0000"+ - "\u0000\u0128\u047e\u0001\u0000\u0000\u0000\u012a\u0482\u0001\u0000\u0000"+ - "\u0000\u012c\u0486\u0001\u0000\u0000\u0000\u012e\u048a\u0001\u0000\u0000"+ - "\u0000\u0130\u048e\u0001\u0000\u0000\u0000\u0132\u0493\u0001\u0000\u0000"+ - "\u0000\u0134\u0498\u0001\u0000\u0000\u0000\u0136\u049c\u0001\u0000\u0000"+ - "\u0000\u0138\u04a0\u0001\u0000\u0000\u0000\u013a\u04a4\u0001\u0000\u0000"+ - "\u0000\u013c\u04a9\u0001\u0000\u0000\u0000\u013e\u04b3\u0001\u0000\u0000"+ - "\u0000\u0140\u04b7\u0001\u0000\u0000\u0000\u0142\u04bb\u0001\u0000\u0000"+ - "\u0000\u0144\u04bf\u0001\u0000\u0000\u0000\u0146\u04c4\u0001\u0000\u0000"+ - "\u0000\u0148\u04cb\u0001\u0000\u0000\u0000\u014a\u04cf\u0001\u0000\u0000"+ - "\u0000\u014c\u04d3\u0001\u0000\u0000\u0000\u014e\u04d7\u0001\u0000\u0000"+ - "\u0000\u0150\u04db\u0001\u0000\u0000\u0000\u0152\u04e0\u0001\u0000\u0000"+ - "\u0000\u0154\u04e6\u0001\u0000\u0000\u0000\u0156\u04ea\u0001\u0000\u0000"+ - "\u0000\u0158\u04ee\u0001\u0000\u0000\u0000\u015a\u04f2\u0001\u0000\u0000"+ - "\u0000\u015c\u04f8\u0001\u0000\u0000\u0000\u015e\u04fc\u0001\u0000\u0000"+ - "\u0000\u0160\u0500\u0001\u0000\u0000\u0000\u0162\u0504\u0001\u0000\u0000"+ - "\u0000\u0164\u050a\u0001\u0000\u0000\u0000\u0166\u0510\u0001\u0000\u0000"+ - "\u0000\u0168\u0516\u0001\u0000\u0000\u0000\u016a\u016b\u0005d\u0000\u0000"+ - "\u016b\u016c\u0005i\u0000\u0000\u016c\u016d\u0005s\u0000\u0000\u016d\u016e"+ - "\u0005s\u0000\u0000\u016e\u016f\u0005e\u0000\u0000\u016f\u0170\u0005c"+ - "\u0000\u0000\u0170\u0171\u0005t\u0000\u0000\u0171\u0172\u0001\u0000\u0000"+ - "\u0000\u0172\u0173\u0006\u0000\u0000\u0000\u0173\u000f\u0001\u0000\u0000"+ - "\u0000\u0174\u0175\u0005d\u0000\u0000\u0175\u0176\u0005r\u0000\u0000\u0176"+ - "\u0177\u0005o\u0000\u0000\u0177\u0178\u0005p\u0000\u0000\u0178\u0179\u0001"+ - "\u0000\u0000\u0000\u0179\u017a\u0006\u0001\u0001\u0000\u017a\u0011\u0001"+ - "\u0000\u0000\u0000\u017b\u017c\u0005e\u0000\u0000\u017c\u017d\u0005n\u0000"+ - "\u0000\u017d\u017e\u0005r\u0000\u0000\u017e\u017f\u0005i\u0000\u0000\u017f"+ - "\u0180\u0005c\u0000\u0000\u0180\u0181\u0005h\u0000\u0000\u0181\u0182\u0001"+ - "\u0000\u0000\u0000\u0182\u0183\u0006\u0002\u0002\u0000\u0183\u0013\u0001"+ - "\u0000\u0000\u0000\u0184\u0185\u0005e\u0000\u0000\u0185\u0186\u0005v\u0000"+ - "\u0000\u0186\u0187\u0005a\u0000\u0000\u0187\u0188\u0005l\u0000\u0000\u0188"+ - "\u0189\u0001\u0000\u0000\u0000\u0189\u018a\u0006\u0003\u0000\u0000\u018a"+ - "\u0015\u0001\u0000\u0000\u0000\u018b\u018c\u0005e\u0000\u0000\u018c\u018d"+ - "\u0005x\u0000\u0000\u018d\u018e\u0005p\u0000\u0000\u018e\u018f\u0005l"+ - "\u0000\u0000\u018f\u0190\u0005a\u0000\u0000\u0190\u0191\u0005i\u0000\u0000"+ - "\u0191\u0192\u0005n\u0000\u0000\u0192\u0193\u0001\u0000\u0000\u0000\u0193"+ - "\u0194\u0006\u0004\u0003\u0000\u0194\u0017\u0001\u0000\u0000\u0000\u0195"+ - "\u0196\u0005f\u0000\u0000\u0196\u0197\u0005r\u0000\u0000\u0197\u0198\u0005"+ - "o\u0000\u0000\u0198\u0199\u0005m\u0000\u0000\u0199\u019a\u0001\u0000\u0000"+ - "\u0000\u019a\u019b\u0006\u0005\u0004\u0000\u019b\u0019\u0001\u0000\u0000"+ - "\u0000\u019c\u019d\u0005g\u0000\u0000\u019d\u019e\u0005r\u0000\u0000\u019e"+ - "\u019f\u0005o\u0000\u0000\u019f\u01a0\u0005k\u0000\u0000\u01a0\u01a1\u0001"+ - "\u0000\u0000\u0000\u01a1\u01a2\u0006\u0006\u0000\u0000\u01a2\u001b\u0001"+ - "\u0000\u0000\u0000\u01a3\u01a4\u0005i\u0000\u0000\u01a4\u01a5\u0005n\u0000"+ - "\u0000\u01a5\u01a6\u0005l\u0000\u0000\u01a6\u01a7\u0005i\u0000\u0000\u01a7"+ - "\u01a8\u0005n\u0000\u0000\u01a8\u01a9\u0005e\u0000\u0000\u01a9\u01aa\u0005"+ - "s\u0000\u0000\u01aa\u01ab\u0005t\u0000\u0000\u01ab\u01ac\u0005a\u0000"+ - "\u0000\u01ac\u01ad\u0005t\u0000\u0000\u01ad\u01ae\u0005s\u0000\u0000\u01ae"+ - "\u01af\u0001\u0000\u0000\u0000\u01af\u01b0\u0006\u0007\u0000\u0000\u01b0"+ - "\u001d\u0001\u0000\u0000\u0000\u01b1\u01b2\u0005k\u0000\u0000\u01b2\u01b3"+ - "\u0005e\u0000\u0000\u01b3\u01b4\u0005e\u0000\u0000\u01b4\u01b5\u0005p"+ - "\u0000\u0000\u01b5\u01b6\u0001\u0000\u0000\u0000\u01b6\u01b7\u0006\b\u0001"+ - "\u0000\u01b7\u001f\u0001\u0000\u0000\u0000\u01b8\u01b9\u0005l\u0000\u0000"+ - "\u01b9\u01ba\u0005i\u0000\u0000\u01ba\u01bb\u0005m\u0000\u0000\u01bb\u01bc"+ - "\u0005i\u0000\u0000\u01bc\u01bd\u0005t\u0000\u0000\u01bd\u01be\u0001\u0000"+ - "\u0000\u0000\u01be\u01bf\u0006\t\u0000\u0000\u01bf!\u0001\u0000\u0000"+ - "\u0000\u01c0\u01c1\u0005m\u0000\u0000\u01c1\u01c2\u0005e\u0000\u0000\u01c2"+ - "\u01c3\u0005t\u0000\u0000\u01c3\u01c4\u0005a\u0000\u0000\u01c4\u01c5\u0001"+ - "\u0000\u0000\u0000\u01c5\u01c6\u0006\n\u0005\u0000\u01c6#\u0001\u0000"+ - "\u0000\u0000\u01c7\u01c8\u0005m\u0000\u0000\u01c8\u01c9\u0005e\u0000\u0000"+ - "\u01c9\u01ca\u0005t\u0000\u0000\u01ca\u01cb\u0005r\u0000\u0000\u01cb\u01cc"+ - "\u0005i\u0000\u0000\u01cc\u01cd\u0005c\u0000\u0000\u01cd\u01ce\u0005s"+ - "\u0000\u0000\u01ce\u01cf\u0001\u0000\u0000\u0000\u01cf\u01d0\u0006\u000b"+ - "\u0006\u0000\u01d0%\u0001\u0000\u0000\u0000\u01d1\u01d2\u0005m\u0000\u0000"+ - "\u01d2\u01d3\u0005v\u0000\u0000\u01d3\u01d4\u0005_\u0000\u0000\u01d4\u01d5"+ - "\u0005e\u0000\u0000\u01d5\u01d6\u0005x\u0000\u0000\u01d6\u01d7\u0005p"+ - "\u0000\u0000\u01d7\u01d8\u0005a\u0000\u0000\u01d8\u01d9\u0005n\u0000\u0000"+ - "\u01d9\u01da\u0005d\u0000\u0000\u01da\u01db\u0001\u0000\u0000\u0000\u01db"+ - "\u01dc\u0006\f\u0007\u0000\u01dc\'\u0001\u0000\u0000\u0000\u01dd\u01de"+ - "\u0005r\u0000\u0000\u01de\u01df\u0005e\u0000\u0000\u01df\u01e0\u0005n"+ - "\u0000\u0000\u01e0\u01e1\u0005a\u0000\u0000\u01e1\u01e2\u0005m\u0000\u0000"+ - "\u01e2\u01e3\u0005e\u0000\u0000\u01e3\u01e4\u0001\u0000\u0000\u0000\u01e4"+ - "\u01e5\u0006\r\b\u0000\u01e5)\u0001\u0000\u0000\u0000\u01e6\u01e7\u0005"+ - "r\u0000\u0000\u01e7\u01e8\u0005o\u0000\u0000\u01e8\u01e9\u0005w\u0000"+ - "\u0000\u01e9\u01ea\u0001\u0000\u0000\u0000\u01ea\u01eb\u0006\u000e\u0000"+ - "\u0000\u01eb+\u0001\u0000\u0000\u0000\u01ec\u01ed\u0005s\u0000\u0000\u01ed"+ - "\u01ee\u0005h\u0000\u0000\u01ee\u01ef\u0005o\u0000\u0000\u01ef\u01f0\u0005"+ - "w\u0000\u0000\u01f0\u01f1\u0001\u0000\u0000\u0000\u01f1\u01f2\u0006\u000f"+ - "\t\u0000\u01f2-\u0001\u0000\u0000\u0000\u01f3\u01f4\u0005s\u0000\u0000"+ - "\u01f4\u01f5\u0005o\u0000\u0000\u01f5\u01f6\u0005r\u0000\u0000\u01f6\u01f7"+ - "\u0005t\u0000\u0000\u01f7\u01f8\u0001\u0000\u0000\u0000\u01f8\u01f9\u0006"+ - "\u0010\u0000\u0000\u01f9/\u0001\u0000\u0000\u0000\u01fa\u01fb\u0005s\u0000"+ - "\u0000\u01fb\u01fc\u0005t\u0000\u0000\u01fc\u01fd\u0005a\u0000\u0000\u01fd"+ - "\u01fe\u0005t\u0000\u0000\u01fe\u01ff\u0005s\u0000\u0000\u01ff\u0200\u0001"+ - "\u0000\u0000\u0000\u0200\u0201\u0006\u0011\u0000\u0000\u02011\u0001\u0000"+ - "\u0000\u0000\u0202\u0203\u0005w\u0000\u0000\u0203\u0204\u0005h\u0000\u0000"+ - "\u0204\u0205\u0005e\u0000\u0000\u0205\u0206\u0005r\u0000\u0000\u0206\u0207"+ - "\u0005e\u0000\u0000\u0207\u0208\u0001\u0000\u0000\u0000\u0208\u0209\u0006"+ - "\u0012\u0000\u0000\u02093\u0001\u0000\u0000\u0000\u020a\u020c\b\u0000"+ - "\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\u0013"+ - "\u0000\u0000\u02105\u0001\u0000\u0000\u0000\u0211\u0212\u0005/\u0000\u0000"+ - "\u0212\u0213\u0005/\u0000\u0000\u0213\u0217\u0001\u0000\u0000\u0000\u0214"+ - "\u0216\b\u0001\u0000\u0000\u0215\u0214\u0001\u0000\u0000\u0000\u0216\u0219"+ - "\u0001\u0000\u0000\u0000\u0217\u0215\u0001\u0000\u0000\u0000\u0217\u0218"+ - "\u0001\u0000\u0000\u0000\u0218\u021b\u0001\u0000\u0000\u0000\u0219\u0217"+ - "\u0001\u0000\u0000\u0000\u021a\u021c\u0005\r\u0000\u0000\u021b\u021a\u0001"+ - "\u0000\u0000\u0000\u021b\u021c\u0001\u0000\u0000\u0000\u021c\u021e\u0001"+ - "\u0000\u0000\u0000\u021d\u021f\u0005\n\u0000\u0000\u021e\u021d\u0001\u0000"+ - "\u0000\u0000\u021e\u021f\u0001\u0000\u0000\u0000\u021f\u0220\u0001\u0000"+ - "\u0000\u0000\u0220\u0221\u0006\u0014\n\u0000\u02217\u0001\u0000\u0000"+ - "\u0000\u0222\u0223\u0005/\u0000\u0000\u0223\u0224\u0005*\u0000\u0000\u0224"+ - "\u0229\u0001\u0000\u0000\u0000\u0225\u0228\u00038\u0015\u0000\u0226\u0228"+ - "\t\u0000\u0000\u0000\u0227\u0225\u0001\u0000\u0000\u0000\u0227\u0226\u0001"+ - "\u0000\u0000\u0000\u0228\u022b\u0001\u0000\u0000\u0000\u0229\u022a\u0001"+ - "\u0000\u0000\u0000\u0229\u0227\u0001\u0000\u0000\u0000\u022a\u022c\u0001"+ - "\u0000\u0000\u0000\u022b\u0229\u0001\u0000\u0000\u0000\u022c\u022d\u0005"+ - "*\u0000\u0000\u022d\u022e\u0005/\u0000\u0000\u022e\u022f\u0001\u0000\u0000"+ - "\u0000\u022f\u0230\u0006\u0015\n\u0000\u02309\u0001\u0000\u0000\u0000"+ - "\u0231\u0233\u0007\u0002\u0000\u0000\u0232\u0231\u0001\u0000\u0000\u0000"+ - "\u0233\u0234\u0001\u0000\u0000\u0000\u0234\u0232\u0001\u0000\u0000\u0000"+ - "\u0234\u0235\u0001\u0000\u0000\u0000\u0235\u0236\u0001\u0000\u0000\u0000"+ - "\u0236\u0237\u0006\u0016\n\u0000\u0237;\u0001\u0000\u0000\u0000\u0238"+ - "\u023c\b\u0003\u0000\u0000\u0239\u023a\u0005/\u0000\u0000\u023a\u023c"+ - "\b\u0004\u0000\u0000\u023b\u0238\u0001\u0000\u0000\u0000\u023b\u0239\u0001"+ - "\u0000\u0000\u0000\u023c=\u0001\u0000\u0000\u0000\u023d\u023f\u0003<\u0017"+ - "\u0000\u023e\u023d\u0001\u0000\u0000\u0000\u023f\u0240\u0001\u0000\u0000"+ - "\u0000\u0240\u023e\u0001\u0000\u0000\u0000\u0240\u0241\u0001\u0000\u0000"+ - "\u0000\u0241?\u0001\u0000\u0000\u0000\u0242\u0243\u0003\u00acO\u0000\u0243"+ - "\u0244\u0001\u0000\u0000\u0000\u0244\u0245\u0006\u0019\u000b\u0000\u0245"+ - "\u0246\u0006\u0019\f\u0000\u0246A\u0001\u0000\u0000\u0000\u0247\u0248"+ - "\u0003J\u001e\u0000\u0248\u0249\u0001\u0000\u0000\u0000\u0249\u024a\u0006"+ - "\u001a\r\u0000\u024a\u024b\u0006\u001a\u000e\u0000\u024bC\u0001\u0000"+ - "\u0000\u0000\u024c\u024d\u0003:\u0016\u0000\u024d\u024e\u0001\u0000\u0000"+ - "\u0000\u024e\u024f\u0006\u001b\n\u0000\u024fE\u0001\u0000\u0000\u0000"+ - "\u0250\u0251\u00036\u0014\u0000\u0251\u0252\u0001\u0000\u0000\u0000\u0252"+ - "\u0253\u0006\u001c\n\u0000\u0253G\u0001\u0000\u0000\u0000\u0254\u0255"+ - "\u00038\u0015\u0000\u0255\u0256\u0001\u0000\u0000\u0000\u0256\u0257\u0006"+ - "\u001d\n\u0000\u0257I\u0001\u0000\u0000\u0000\u0258\u0259\u0005|\u0000"+ - "\u0000\u0259\u025a\u0001\u0000\u0000\u0000\u025a\u025b\u0006\u001e\u000e"+ - "\u0000\u025bK\u0001\u0000\u0000\u0000\u025c\u025d\u0007\u0005\u0000\u0000"+ - "\u025dM\u0001\u0000\u0000\u0000\u025e\u025f\u0007\u0006\u0000\u0000\u025f"+ - "O\u0001\u0000\u0000\u0000\u0260\u0261\u0005\\\u0000\u0000\u0261\u0262"+ - "\u0007\u0007\u0000\u0000\u0262Q\u0001\u0000\u0000\u0000\u0263\u0264\b"+ - "\b\u0000\u0000\u0264S\u0001\u0000\u0000\u0000\u0265\u0267\u0007\t\u0000"+ - "\u0000\u0266\u0268\u0007\n\u0000\u0000\u0267\u0266\u0001\u0000\u0000\u0000"+ - "\u0267\u0268\u0001\u0000\u0000\u0000\u0268\u026a\u0001\u0000\u0000\u0000"+ - "\u0269\u026b\u0003L\u001f\u0000\u026a\u0269\u0001\u0000\u0000\u0000\u026b"+ - "\u026c\u0001\u0000\u0000\u0000\u026c\u026a\u0001\u0000\u0000\u0000\u026c"+ - "\u026d\u0001\u0000\u0000\u0000\u026dU\u0001\u0000\u0000\u0000\u026e\u026f"+ - "\u0005@\u0000\u0000\u026fW\u0001\u0000\u0000\u0000\u0270\u0271\u0005`"+ - "\u0000\u0000\u0271Y\u0001\u0000\u0000\u0000\u0272\u0276\b\u000b\u0000"+ - "\u0000\u0273\u0274\u0005`\u0000\u0000\u0274\u0276\u0005`\u0000\u0000\u0275"+ - "\u0272\u0001\u0000\u0000\u0000\u0275\u0273\u0001\u0000\u0000\u0000\u0276"+ - "[\u0001\u0000\u0000\u0000\u0277\u0278\u0005_\u0000\u0000\u0278]\u0001"+ - "\u0000\u0000\u0000\u0279\u027d\u0003N \u0000\u027a\u027d\u0003L\u001f"+ - "\u0000\u027b\u027d\u0003\\\'\u0000\u027c\u0279\u0001\u0000\u0000\u0000"+ - "\u027c\u027a\u0001\u0000\u0000\u0000\u027c\u027b\u0001\u0000\u0000\u0000"+ - "\u027d_\u0001\u0000\u0000\u0000\u027e\u0283\u0005\"\u0000\u0000\u027f"+ - "\u0282\u0003P!\u0000\u0280\u0282\u0003R\"\u0000\u0281\u027f\u0001\u0000"+ - "\u0000\u0000\u0281\u0280\u0001\u0000\u0000\u0000\u0282\u0285\u0001\u0000"+ - "\u0000\u0000\u0283\u0281\u0001\u0000\u0000\u0000\u0283\u0284\u0001\u0000"+ - "\u0000\u0000\u0284\u0286\u0001\u0000\u0000\u0000\u0285\u0283\u0001\u0000"+ - "\u0000\u0000\u0286\u029c\u0005\"\u0000\u0000\u0287\u0288\u0005\"\u0000"+ - "\u0000\u0288\u0289\u0005\"\u0000\u0000\u0289\u028a\u0005\"\u0000\u0000"+ - "\u028a\u028e\u0001\u0000\u0000\u0000\u028b\u028d\b\u0001\u0000\u0000\u028c"+ - "\u028b\u0001\u0000\u0000\u0000\u028d\u0290\u0001\u0000\u0000\u0000\u028e"+ + "\u000b\u0001\u000b\u0001\f\u0001\f\u0001\f\u0001\f\u0001\f\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\r\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\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\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001"+ + "\u0012\u0001\u0012\u0001\u0012\u0001\u0012\u0001\u0013\u0004\u0013\u020a"+ + "\b\u0013\u000b\u0013\f\u0013\u020b\u0001\u0013\u0001\u0013\u0001\u0014"+ + "\u0001\u0014\u0001\u0014\u0001\u0014\u0005\u0014\u0214\b\u0014\n\u0014"+ + "\f\u0014\u0217\t\u0014\u0001\u0014\u0003\u0014\u021a\b\u0014\u0001\u0014"+ + "\u0003\u0014\u021d\b\u0014\u0001\u0014\u0001\u0014\u0001\u0015\u0001\u0015"+ + "\u0001\u0015\u0001\u0015\u0001\u0015\u0005\u0015\u0226\b\u0015\n\u0015"+ + "\f\u0015\u0229\t\u0015\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015"+ + "\u0001\u0015\u0001\u0016\u0004\u0016\u0231\b\u0016\u000b\u0016\f\u0016"+ + "\u0232\u0001\u0016\u0001\u0016\u0001\u0017\u0001\u0017\u0001\u0017\u0003"+ + "\u0017\u023a\b\u0017\u0001\u0018\u0004\u0018\u023d\b\u0018\u000b\u0018"+ + "\f\u0018\u023e\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019\u0001\u0019"+ + "\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001a\u0001\u001b"+ + "\u0001\u001b\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001\u001c"+ + "\u0001\u001c\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001d\u0001\u001e"+ + "\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001f\u0001\u001f\u0001 \u0001"+ + " \u0001!\u0001!\u0001!\u0001\"\u0001\"\u0001#\u0001#\u0003#\u0266\b#\u0001"+ + "#\u0004#\u0269\b#\u000b#\f#\u026a\u0001$\u0001$\u0001%\u0001%\u0001&\u0001"+ + "&\u0001&\u0003&\u0274\b&\u0001\'\u0001\'\u0001(\u0001(\u0001(\u0003(\u027b"+ + "\b(\u0001)\u0001)\u0001)\u0005)\u0280\b)\n)\f)\u0283\t)\u0001)\u0001)"+ + "\u0001)\u0001)\u0001)\u0001)\u0005)\u028b\b)\n)\f)\u028e\t)\u0001)\u0001"+ + ")\u0001)\u0001)\u0001)\u0003)\u0295\b)\u0001)\u0003)\u0298\b)\u0003)\u029a"+ + "\b)\u0001*\u0004*\u029d\b*\u000b*\f*\u029e\u0001+\u0004+\u02a2\b+\u000b"+ + "+\f+\u02a3\u0001+\u0001+\u0005+\u02a8\b+\n+\f+\u02ab\t+\u0001+\u0001+"+ + "\u0004+\u02af\b+\u000b+\f+\u02b0\u0001+\u0004+\u02b4\b+\u000b+\f+\u02b5"+ + "\u0001+\u0001+\u0005+\u02ba\b+\n+\f+\u02bd\t+\u0003+\u02bf\b+\u0001+\u0001"+ + "+\u0001+\u0001+\u0004+\u02c5\b+\u000b+\f+\u02c6\u0001+\u0001+\u0003+\u02cb"+ + "\b+\u0001,\u0001,\u0001,\u0001-\u0001-\u0001-\u0001-\u0001.\u0001.\u0001"+ + ".\u0001.\u0001/\u0001/\u00010\u00010\u00010\u00011\u00011\u00012\u0001"+ + "2\u00012\u00012\u00012\u00013\u00013\u00014\u00014\u00014\u00014\u0001"+ + "4\u00014\u00015\u00015\u00015\u00015\u00015\u00015\u00016\u00016\u0001"+ + "6\u00016\u00016\u00017\u00017\u00018\u00018\u00018\u00019\u00019\u0001"+ + "9\u0001:\u0001:\u0001:\u0001:\u0001:\u0001;\u0001;\u0001;\u0001;\u0001"+ + "<\u0001<\u0001<\u0001<\u0001<\u0001=\u0001=\u0001=\u0001=\u0001=\u0001"+ + "=\u0001>\u0001>\u0001>\u0001?\u0001?\u0001@\u0001@\u0001@\u0001@\u0001"+ + "@\u0001@\u0001A\u0001A\u0001B\u0001B\u0001B\u0001B\u0001B\u0001C\u0001"+ + "C\u0001C\u0001D\u0001D\u0001D\u0001E\u0001E\u0001E\u0001F\u0001F\u0001"+ + "G\u0001G\u0001G\u0001H\u0001H\u0001I\u0001I\u0001I\u0001J\u0001J\u0001"+ + "K\u0001K\u0001L\u0001L\u0001M\u0001M\u0001N\u0001N\u0001O\u0001O\u0001"+ + "O\u0001O\u0001O\u0001P\u0001P\u0001P\u0001P\u0001P\u0001Q\u0001Q\u0005"+ + "Q\u034e\bQ\nQ\fQ\u0351\tQ\u0001Q\u0001Q\u0003Q\u0355\bQ\u0001Q\u0004Q"+ + "\u0358\bQ\u000bQ\fQ\u0359\u0003Q\u035c\bQ\u0001R\u0001R\u0004R\u0360\b"+ + "R\u000bR\fR\u0361\u0001R\u0001R\u0001S\u0001S\u0001T\u0001T\u0001T\u0001"+ + "T\u0001U\u0001U\u0001U\u0001U\u0001V\u0001V\u0001V\u0001V\u0001W\u0001"+ + "W\u0001W\u0001W\u0001W\u0001X\u0001X\u0001X\u0001X\u0001Y\u0001Y\u0001"+ + "Y\u0001Y\u0001Z\u0001Z\u0001Z\u0001Z\u0001[\u0001[\u0001[\u0001[\u0001"+ + "\\\u0001\\\u0001\\\u0001\\\u0001]\u0001]\u0001]\u0001]\u0001]\u0001]\u0001"+ + "]\u0001]\u0001]\u0001^\u0001^\u0001^\u0001^\u0001_\u0001_\u0001_\u0001"+ + "_\u0001`\u0001`\u0001`\u0001`\u0001a\u0001a\u0001a\u0001a\u0001b\u0001"+ + "b\u0001b\u0001b\u0001b\u0001c\u0001c\u0001c\u0001c\u0001d\u0001d\u0001"+ + "d\u0001d\u0001e\u0001e\u0001e\u0001e\u0003e\u03b7\be\u0001f\u0001f\u0003"+ + "f\u03bb\bf\u0001f\u0005f\u03be\bf\nf\ff\u03c1\tf\u0001f\u0001f\u0003f"+ + "\u03c5\bf\u0001f\u0004f\u03c8\bf\u000bf\ff\u03c9\u0003f\u03cc\bf\u0001"+ + "g\u0001g\u0004g\u03d0\bg\u000bg\fg\u03d1\u0001h\u0001h\u0001h\u0001h\u0001"+ + "i\u0001i\u0001i\u0001i\u0001j\u0001j\u0001j\u0001j\u0001k\u0001k\u0001"+ + "k\u0001k\u0001k\u0001l\u0001l\u0001l\u0001l\u0001m\u0001m\u0001m\u0001"+ + "m\u0001n\u0001n\u0001n\u0001n\u0001o\u0001o\u0001o\u0001p\u0001p\u0001"+ + "p\u0001p\u0001q\u0001q\u0001q\u0001q\u0001r\u0001r\u0001r\u0001r\u0001"+ + "s\u0001s\u0001s\u0001s\u0001t\u0001t\u0001t\u0001t\u0001t\u0001u\u0001"+ + "u\u0001u\u0001u\u0001u\u0001v\u0001v\u0001v\u0001v\u0001v\u0001w\u0001"+ + "w\u0001w\u0001w\u0001w\u0001w\u0001w\u0001x\u0001x\u0001y\u0004y\u041d"+ + "\by\u000by\fy\u041e\u0001y\u0001y\u0003y\u0423\by\u0001y\u0004y\u0426"+ + "\by\u000by\fy\u0427\u0001z\u0001z\u0001z\u0001z\u0001{\u0001{\u0001{\u0001"+ + "{\u0001|\u0001|\u0001|\u0001|\u0001}\u0001}\u0001}\u0001}\u0001~\u0001"+ + "~\u0001~\u0001~\u0001\u007f\u0001\u007f\u0001\u007f\u0001\u007f\u0001"+ + "\u007f\u0001\u007f\u0001\u0080\u0001\u0080\u0001\u0080\u0001\u0080\u0001"+ + "\u0081\u0001\u0081\u0001\u0081\u0001\u0081\u0001\u0082\u0001\u0082\u0001"+ + "\u0082\u0001\u0082\u0001\u0083\u0001\u0083\u0001\u0083\u0001\u0083\u0001"+ + "\u0084\u0001\u0084\u0001\u0084\u0001\u0084\u0001\u0085\u0001\u0085\u0001"+ + "\u0085\u0001\u0085\u0001\u0086\u0001\u0086\u0001\u0086\u0001\u0086\u0001"+ + "\u0087\u0001\u0087\u0001\u0087\u0001\u0087\u0001\u0088\u0001\u0088\u0001"+ + "\u0088\u0001\u0088\u0001\u0089\u0001\u0089\u0001\u0089\u0001\u0089\u0001"+ + "\u0089\u0001\u008a\u0001\u008a\u0001\u008a\u0001\u008a\u0001\u008b\u0001"+ + "\u008b\u0001\u008b\u0001\u008b\u0001\u008c\u0001\u008c\u0001\u008c\u0001"+ + "\u008c\u0001\u008d\u0001\u008d\u0001\u008d\u0001\u008d\u0001\u008e\u0001"+ + "\u008e\u0001\u008e\u0001\u008e\u0001\u008f\u0001\u008f\u0001\u008f\u0001"+ + "\u008f\u0001\u0090\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\u0095\u0001\u0096\u0001\u0096\u0001"+ + "\u0096\u0001\u0096\u0001\u0096\u0001\u0096\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\u009a\u0001\u009a\u0001\u009a\u0001\u009a\u0001"+ + "\u009a\u0001\u009b\u0001\u009b\u0001\u009c\u0001\u009c\u0001\u009c\u0001"+ + "\u009c\u0001\u009c\u0004\u009c\u04c2\b\u009c\u000b\u009c\f\u009c\u04c3"+ + "\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\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\u00a5\u0001\u00a6"+ + "\u0001\u00a6\u0001\u00a6\u0001\u00a6\u0001\u00a7\u0001\u00a7\u0001\u00a7"+ + "\u0001\u00a7\u0001\u00a8\u0001\u00a8\u0001\u00a8\u0001\u00a8\u0001\u00a9"+ + "\u0001\u00a9\u0001\u00a9\u0001\u00a9\u0001\u00a9\u0001\u00a9\u0001\u00aa"+ + "\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00aa\u0001\u00ab"+ + "\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001\u00ab\u0001\u00ac"+ + "\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0001\u00ac\u0002\u0227\u028c\u0000"+ + "\u00ad\u000e\u0001\u0010\u0002\u0012\u0003\u0014\u0004\u0016\u0005\u0018"+ + "\u0006\u001a\u0007\u001c\b\u001e\t \n\"\u000b$\f&\r(\u000e*\u000f,\u0010"+ + ".\u00110\u00122\u00134\u00146\u00158\u0016:\u0017<\u0000>\u0018@\u0000"+ + "B\u0000D\u0019F\u001aH\u001bJ\u001cL\u0000N\u0000P\u0000R\u0000T\u0000"+ + "V\u0000X\u0000Z\u0000\\\u0000^\u0000`\u001db\u001ed\u001ff h!j\"l#n$p"+ + "%r&t\'v(x)z*|+~,\u0080-\u0082.\u0084/\u00860\u00881\u008a2\u008c3\u008e"+ + "4\u00905\u00926\u00947\u00968\u00989\u009a:\u009c;\u009e<\u00a0=\u00a2"+ + ">\u00a4?\u00a6@\u00a8A\u00aaB\u00acC\u00aeD\u00b0E\u00b2\u0000\u00b4F"+ + "\u00b6G\u00b8H\u00baI\u00bc\u0000\u00be\u0000\u00c0\u0000\u00c2\u0000"+ + "\u00c4\u0000\u00c6\u0000\u00c8J\u00ca\u0000\u00ccK\u00ceL\u00d0M\u00d2"+ + "\u0000\u00d4\u0000\u00d6\u0000\u00d8\u0000\u00da\u0000\u00dcN\u00deO\u00e0"+ + "P\u00e2Q\u00e4\u0000\u00e6\u0000\u00e8\u0000\u00ea\u0000\u00ecR\u00ee"+ + "\u0000\u00f0S\u00f2T\u00f4U\u00f6\u0000\u00f8\u0000\u00faV\u00fcW\u00fe"+ + "\u0000\u0100X\u0102\u0000\u0104\u0000\u0106Y\u0108Z\u010a[\u010c\u0000"+ + "\u010e\u0000\u0110\u0000\u0112\u0000\u0114\u0000\u0116\u0000\u0118\u0000"+ + "\u011a\\\u011c]\u011e^\u0120\u0000\u0122\u0000\u0124\u0000\u0126\u0000"+ + "\u0128_\u012a`\u012ca\u012e\u0000\u0130b\u0132c\u0134d\u0136e\u0138\u0000"+ + "\u013af\u013cg\u013eh\u0140i\u0142\u0000\u0144j\u0146k\u0148l\u014am\u014c"+ + "n\u014e\u0000\u0150\u0000\u0152o\u0154p\u0156q\u0158\u0000\u015ar\u015c"+ + "s\u015et\u0160\u0000\u0162\u0000\u0164\u0000\u0166\u0000\u000e\u0000\u0001"+ + "\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\r\u0006\u0000\t\n"+ + "\r\r //[[]]\u0002\u0000\n\n\r\r\u0003\u0000\t\n\r\r \n\u0000\t\n\r\r"+ + " ,,//==[[]]``||\u0002\u0000**//\u0001\u000009\u0002\u0000AZaz\u0005\u0000"+ + "\"\"\\\\nnrrtt\u0004\u0000\n\n\r\r\"\"\\\\\u0002\u0000EEee\u0002\u0000"+ + "++--\u0001\u0000``\u000b\u0000\t\n\r\r \"#,,//::<<>?\\\\||\u052a\u0000"+ + "\u000e\u0001\u0000\u0000\u0000\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\u0001@"+ + "\u0001\u0000\u0000\u0000\u0001B\u0001\u0000\u0000\u0000\u0001D\u0001\u0000"+ + "\u0000\u0000\u0001F\u0001\u0000\u0000\u0000\u0001H\u0001\u0000\u0000\u0000"+ + "\u0002J\u0001\u0000\u0000\u0000\u0002`\u0001\u0000\u0000\u0000\u0002b"+ + "\u0001\u0000\u0000\u0000\u0002d\u0001\u0000\u0000\u0000\u0002f\u0001\u0000"+ + "\u0000\u0000\u0002h\u0001\u0000\u0000\u0000\u0002j\u0001\u0000\u0000\u0000"+ + "\u0002l\u0001\u0000\u0000\u0000\u0002n\u0001\u0000\u0000\u0000\u0002p"+ + "\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\u0002|\u0001\u0000\u0000\u0000\u0002~"+ + "\u0001\u0000\u0000\u0000\u0002\u0080\u0001\u0000\u0000\u0000\u0002\u0082"+ + "\u0001\u0000\u0000\u0000\u0002\u0084\u0001\u0000\u0000\u0000\u0002\u0086"+ + "\u0001\u0000\u0000\u0000\u0002\u0088\u0001\u0000\u0000\u0000\u0002\u008a"+ + "\u0001\u0000\u0000\u0000\u0002\u008c\u0001\u0000\u0000\u0000\u0002\u008e"+ + "\u0001\u0000\u0000\u0000\u0002\u0090\u0001\u0000\u0000\u0000\u0002\u0092"+ + "\u0001\u0000\u0000\u0000\u0002\u0094\u0001\u0000\u0000\u0000\u0002\u0096"+ + "\u0001\u0000\u0000\u0000\u0002\u0098\u0001\u0000\u0000\u0000\u0002\u009a"+ + "\u0001\u0000\u0000\u0000\u0002\u009c\u0001\u0000\u0000\u0000\u0002\u009e"+ + "\u0001\u0000\u0000\u0000\u0002\u00a0\u0001\u0000\u0000\u0000\u0002\u00a2"+ + "\u0001\u0000\u0000\u0000\u0002\u00a4\u0001\u0000\u0000\u0000\u0002\u00a6"+ + "\u0001\u0000\u0000\u0000\u0002\u00a8\u0001\u0000\u0000\u0000\u0002\u00aa"+ + "\u0001\u0000\u0000\u0000\u0002\u00ac\u0001\u0000\u0000\u0000\u0002\u00ae"+ + "\u0001\u0000\u0000\u0000\u0002\u00b0\u0001\u0000\u0000\u0000\u0002\u00b4"+ + "\u0001\u0000\u0000\u0000\u0002\u00b6\u0001\u0000\u0000\u0000\u0002\u00b8"+ + "\u0001\u0000\u0000\u0000\u0002\u00ba\u0001\u0000\u0000\u0000\u0003\u00bc"+ + "\u0001\u0000\u0000\u0000\u0003\u00be\u0001\u0000\u0000\u0000\u0003\u00c0"+ + "\u0001\u0000\u0000\u0000\u0003\u00c2\u0001\u0000\u0000\u0000\u0003\u00c4"+ + "\u0001\u0000\u0000\u0000\u0003\u00c6\u0001\u0000\u0000\u0000\u0003\u00c8"+ + "\u0001\u0000\u0000\u0000\u0003\u00ca\u0001\u0000\u0000\u0000\u0003\u00cc"+ + "\u0001\u0000\u0000\u0000\u0003\u00ce\u0001\u0000\u0000\u0000\u0003\u00d0"+ + "\u0001\u0000\u0000\u0000\u0004\u00d2\u0001\u0000\u0000\u0000\u0004\u00d4"+ + "\u0001\u0000\u0000\u0000\u0004\u00d6\u0001\u0000\u0000\u0000\u0004\u00dc"+ + "\u0001\u0000\u0000\u0000\u0004\u00de\u0001\u0000\u0000\u0000\u0004\u00e0"+ + "\u0001\u0000\u0000\u0000\u0004\u00e2\u0001\u0000\u0000\u0000\u0005\u00e4"+ + "\u0001\u0000\u0000\u0000\u0005\u00e6\u0001\u0000\u0000\u0000\u0005\u00e8"+ + "\u0001\u0000\u0000\u0000\u0005\u00ea\u0001\u0000\u0000\u0000\u0005\u00ec"+ + "\u0001\u0000\u0000\u0000\u0005\u00ee\u0001\u0000\u0000\u0000\u0005\u00f0"+ + "\u0001\u0000\u0000\u0000\u0005\u00f2\u0001\u0000\u0000\u0000\u0005\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\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\u0007\u010c\u0001\u0000\u0000\u0000\u0007\u010e"+ + "\u0001\u0000\u0000\u0000\u0007\u0110\u0001\u0000\u0000\u0000\u0007\u0112"+ + "\u0001\u0000\u0000\u0000\u0007\u0114\u0001\u0000\u0000\u0000\u0007\u0116"+ + "\u0001\u0000\u0000\u0000\u0007\u0118\u0001\u0000\u0000\u0000\u0007\u011a"+ + "\u0001\u0000\u0000\u0000\u0007\u011c\u0001\u0000\u0000\u0000\u0007\u011e"+ + "\u0001\u0000\u0000\u0000\b\u0120\u0001\u0000\u0000\u0000\b\u0122\u0001"+ + "\u0000\u0000\u0000\b\u0124\u0001\u0000\u0000\u0000\b\u0126\u0001\u0000"+ + "\u0000\u0000\b\u0128\u0001\u0000\u0000\u0000\b\u012a\u0001\u0000\u0000"+ + "\u0000\b\u012c\u0001\u0000\u0000\u0000\t\u012e\u0001\u0000\u0000\u0000"+ + "\t\u0130\u0001\u0000\u0000\u0000\t\u0132\u0001\u0000\u0000\u0000\t\u0134"+ + "\u0001\u0000\u0000\u0000\t\u0136\u0001\u0000\u0000\u0000\n\u0138\u0001"+ + "\u0000\u0000\u0000\n\u013a\u0001\u0000\u0000\u0000\n\u013c\u0001\u0000"+ + "\u0000\u0000\n\u013e\u0001\u0000\u0000\u0000\n\u0140\u0001\u0000\u0000"+ + "\u0000\u000b\u0142\u0001\u0000\u0000\u0000\u000b\u0144\u0001\u0000\u0000"+ + "\u0000\u000b\u0146\u0001\u0000\u0000\u0000\u000b\u0148\u0001\u0000\u0000"+ + "\u0000\u000b\u014a\u0001\u0000\u0000\u0000\u000b\u014c\u0001\u0000\u0000"+ + "\u0000\f\u014e\u0001\u0000\u0000\u0000\f\u0150\u0001\u0000\u0000\u0000"+ + "\f\u0152\u0001\u0000\u0000\u0000\f\u0154\u0001\u0000\u0000\u0000\f\u0156"+ + "\u0001\u0000\u0000\u0000\r\u0158\u0001\u0000\u0000\u0000\r\u015a\u0001"+ + "\u0000\u0000\u0000\r\u015c\u0001\u0000\u0000\u0000\r\u015e\u0001\u0000"+ + "\u0000\u0000\r\u0160\u0001\u0000\u0000\u0000\r\u0162\u0001\u0000\u0000"+ + "\u0000\r\u0164\u0001\u0000\u0000\u0000\r\u0166\u0001\u0000\u0000\u0000"+ + "\u000e\u0168\u0001\u0000\u0000\u0000\u0010\u0172\u0001\u0000\u0000\u0000"+ + "\u0012\u0179\u0001\u0000\u0000\u0000\u0014\u0182\u0001\u0000\u0000\u0000"+ + "\u0016\u0189\u0001\u0000\u0000\u0000\u0018\u0193\u0001\u0000\u0000\u0000"+ + "\u001a\u019a\u0001\u0000\u0000\u0000\u001c\u01a1\u0001\u0000\u0000\u0000"+ + "\u001e\u01af\u0001\u0000\u0000\u0000 \u01b6\u0001\u0000\u0000\u0000\""+ + "\u01be\u0001\u0000\u0000\u0000$\u01c5\u0001\u0000\u0000\u0000&\u01cf\u0001"+ + "\u0000\u0000\u0000(\u01db\u0001\u0000\u0000\u0000*\u01e4\u0001\u0000\u0000"+ + "\u0000,\u01ea\u0001\u0000\u0000\u0000.\u01f1\u0001\u0000\u0000\u00000"+ + "\u01f8\u0001\u0000\u0000\u00002\u0200\u0001\u0000\u0000\u00004\u0209\u0001"+ + "\u0000\u0000\u00006\u020f\u0001\u0000\u0000\u00008\u0220\u0001\u0000\u0000"+ + "\u0000:\u0230\u0001\u0000\u0000\u0000<\u0239\u0001\u0000\u0000\u0000>"+ + "\u023c\u0001\u0000\u0000\u0000@\u0240\u0001\u0000\u0000\u0000B\u0245\u0001"+ + "\u0000\u0000\u0000D\u024a\u0001\u0000\u0000\u0000F\u024e\u0001\u0000\u0000"+ + "\u0000H\u0252\u0001\u0000\u0000\u0000J\u0256\u0001\u0000\u0000\u0000L"+ + "\u025a\u0001\u0000\u0000\u0000N\u025c\u0001\u0000\u0000\u0000P\u025e\u0001"+ + "\u0000\u0000\u0000R\u0261\u0001\u0000\u0000\u0000T\u0263\u0001\u0000\u0000"+ + "\u0000V\u026c\u0001\u0000\u0000\u0000X\u026e\u0001\u0000\u0000\u0000Z"+ + "\u0273\u0001\u0000\u0000\u0000\\\u0275\u0001\u0000\u0000\u0000^\u027a"+ + "\u0001\u0000\u0000\u0000`\u0299\u0001\u0000\u0000\u0000b\u029c\u0001\u0000"+ + "\u0000\u0000d\u02ca\u0001\u0000\u0000\u0000f\u02cc\u0001\u0000\u0000\u0000"+ + "h\u02cf\u0001\u0000\u0000\u0000j\u02d3\u0001\u0000\u0000\u0000l\u02d7"+ + "\u0001\u0000\u0000\u0000n\u02d9\u0001\u0000\u0000\u0000p\u02dc\u0001\u0000"+ + "\u0000\u0000r\u02de\u0001\u0000\u0000\u0000t\u02e3\u0001\u0000\u0000\u0000"+ + "v\u02e5\u0001\u0000\u0000\u0000x\u02eb\u0001\u0000\u0000\u0000z\u02f1"+ + "\u0001\u0000\u0000\u0000|\u02f6\u0001\u0000\u0000\u0000~\u02f8\u0001\u0000"+ + "\u0000\u0000\u0080\u02fb\u0001\u0000\u0000\u0000\u0082\u02fe\u0001\u0000"+ + "\u0000\u0000\u0084\u0303\u0001\u0000\u0000\u0000\u0086\u0307\u0001\u0000"+ + "\u0000\u0000\u0088\u030c\u0001\u0000\u0000\u0000\u008a\u0312\u0001\u0000"+ + "\u0000\u0000\u008c\u0315\u0001\u0000\u0000\u0000\u008e\u0317\u0001\u0000"+ + "\u0000\u0000\u0090\u031d\u0001\u0000\u0000\u0000\u0092\u031f\u0001\u0000"+ + "\u0000\u0000\u0094\u0324\u0001\u0000\u0000\u0000\u0096\u0327\u0001\u0000"+ + "\u0000\u0000\u0098\u032a\u0001\u0000\u0000\u0000\u009a\u032d\u0001\u0000"+ + "\u0000\u0000\u009c\u032f\u0001\u0000\u0000\u0000\u009e\u0332\u0001\u0000"+ + "\u0000\u0000\u00a0\u0334\u0001\u0000\u0000\u0000\u00a2\u0337\u0001\u0000"+ + "\u0000\u0000\u00a4\u0339\u0001\u0000\u0000\u0000\u00a6\u033b\u0001\u0000"+ + "\u0000\u0000\u00a8\u033d\u0001\u0000\u0000\u0000\u00aa\u033f\u0001\u0000"+ + "\u0000\u0000\u00ac\u0341\u0001\u0000\u0000\u0000\u00ae\u0346\u0001\u0000"+ + "\u0000\u0000\u00b0\u035b\u0001\u0000\u0000\u0000\u00b2\u035d\u0001\u0000"+ + "\u0000\u0000\u00b4\u0365\u0001\u0000\u0000\u0000\u00b6\u0367\u0001\u0000"+ + "\u0000\u0000\u00b8\u036b\u0001\u0000\u0000\u0000\u00ba\u036f\u0001\u0000"+ + "\u0000\u0000\u00bc\u0373\u0001\u0000\u0000\u0000\u00be\u0378\u0001\u0000"+ + "\u0000\u0000\u00c0\u037c\u0001\u0000\u0000\u0000\u00c2\u0380\u0001\u0000"+ + "\u0000\u0000\u00c4\u0384\u0001\u0000\u0000\u0000\u00c6\u0388\u0001\u0000"+ + "\u0000\u0000\u00c8\u038c\u0001\u0000\u0000\u0000\u00ca\u0395\u0001\u0000"+ + "\u0000\u0000\u00cc\u0399\u0001\u0000\u0000\u0000\u00ce\u039d\u0001\u0000"+ + "\u0000\u0000\u00d0\u03a1\u0001\u0000\u0000\u0000\u00d2\u03a5\u0001\u0000"+ + "\u0000\u0000\u00d4\u03aa\u0001\u0000\u0000\u0000\u00d6\u03ae\u0001\u0000"+ + "\u0000\u0000\u00d8\u03b6\u0001\u0000\u0000\u0000\u00da\u03cb\u0001\u0000"+ + "\u0000\u0000\u00dc\u03cf\u0001\u0000\u0000\u0000\u00de\u03d3\u0001\u0000"+ + "\u0000\u0000\u00e0\u03d7\u0001\u0000\u0000\u0000\u00e2\u03db\u0001\u0000"+ + "\u0000\u0000\u00e4\u03df\u0001\u0000\u0000\u0000\u00e6\u03e4\u0001\u0000"+ + "\u0000\u0000\u00e8\u03e8\u0001\u0000\u0000\u0000\u00ea\u03ec\u0001\u0000"+ + "\u0000\u0000\u00ec\u03f0\u0001\u0000\u0000\u0000\u00ee\u03f3\u0001\u0000"+ + "\u0000\u0000\u00f0\u03f7\u0001\u0000\u0000\u0000\u00f2\u03fb\u0001\u0000"+ + "\u0000\u0000\u00f4\u03ff\u0001\u0000\u0000\u0000\u00f6\u0403\u0001\u0000"+ + "\u0000\u0000\u00f8\u0408\u0001\u0000\u0000\u0000\u00fa\u040d\u0001\u0000"+ + "\u0000\u0000\u00fc\u0412\u0001\u0000\u0000\u0000\u00fe\u0419\u0001\u0000"+ + "\u0000\u0000\u0100\u0422\u0001\u0000\u0000\u0000\u0102\u0429\u0001\u0000"+ + "\u0000\u0000\u0104\u042d\u0001\u0000\u0000\u0000\u0106\u0431\u0001\u0000"+ + "\u0000\u0000\u0108\u0435\u0001\u0000\u0000\u0000\u010a\u0439\u0001\u0000"+ + "\u0000\u0000\u010c\u043d\u0001\u0000\u0000\u0000\u010e\u0443\u0001\u0000"+ + "\u0000\u0000\u0110\u0447\u0001\u0000\u0000\u0000\u0112\u044b\u0001\u0000"+ + "\u0000\u0000\u0114\u044f\u0001\u0000\u0000\u0000\u0116\u0453\u0001\u0000"+ + "\u0000\u0000\u0118\u0457\u0001\u0000\u0000\u0000\u011a\u045b\u0001\u0000"+ + "\u0000\u0000\u011c\u045f\u0001\u0000\u0000\u0000\u011e\u0463\u0001\u0000"+ + "\u0000\u0000\u0120\u0467\u0001\u0000\u0000\u0000\u0122\u046c\u0001\u0000"+ + "\u0000\u0000\u0124\u0470\u0001\u0000\u0000\u0000\u0126\u0474\u0001\u0000"+ + "\u0000\u0000\u0128\u0478\u0001\u0000\u0000\u0000\u012a\u047c\u0001\u0000"+ + "\u0000\u0000\u012c\u0480\u0001\u0000\u0000\u0000\u012e\u0484\u0001\u0000"+ + "\u0000\u0000\u0130\u0489\u0001\u0000\u0000\u0000\u0132\u048e\u0001\u0000"+ + "\u0000\u0000\u0134\u0492\u0001\u0000\u0000\u0000\u0136\u0496\u0001\u0000"+ + "\u0000\u0000\u0138\u049a\u0001\u0000\u0000\u0000\u013a\u049f\u0001\u0000"+ + "\u0000\u0000\u013c\u04a9\u0001\u0000\u0000\u0000\u013e\u04ad\u0001\u0000"+ + "\u0000\u0000\u0140\u04b1\u0001\u0000\u0000\u0000\u0142\u04b5\u0001\u0000"+ + "\u0000\u0000\u0144\u04ba\u0001\u0000\u0000\u0000\u0146\u04c1\u0001\u0000"+ + "\u0000\u0000\u0148\u04c5\u0001\u0000\u0000\u0000\u014a\u04c9\u0001\u0000"+ + "\u0000\u0000\u014c\u04cd\u0001\u0000\u0000\u0000\u014e\u04d1\u0001\u0000"+ + "\u0000\u0000\u0150\u04d6\u0001\u0000\u0000\u0000\u0152\u04dc\u0001\u0000"+ + "\u0000\u0000\u0154\u04e0\u0001\u0000\u0000\u0000\u0156\u04e4\u0001\u0000"+ + "\u0000\u0000\u0158\u04e8\u0001\u0000\u0000\u0000\u015a\u04ee\u0001\u0000"+ + "\u0000\u0000\u015c\u04f2\u0001\u0000\u0000\u0000\u015e\u04f6\u0001\u0000"+ + "\u0000\u0000\u0160\u04fa\u0001\u0000\u0000\u0000\u0162\u0500\u0001\u0000"+ + "\u0000\u0000\u0164\u0506\u0001\u0000\u0000\u0000\u0166\u050c\u0001\u0000"+ + "\u0000\u0000\u0168\u0169\u0005d\u0000\u0000\u0169\u016a\u0005i\u0000\u0000"+ + "\u016a\u016b\u0005s\u0000\u0000\u016b\u016c\u0005s\u0000\u0000\u016c\u016d"+ + "\u0005e\u0000\u0000\u016d\u016e\u0005c\u0000\u0000\u016e\u016f\u0005t"+ + "\u0000\u0000\u016f\u0170\u0001\u0000\u0000\u0000\u0170\u0171\u0006\u0000"+ + "\u0000\u0000\u0171\u000f\u0001\u0000\u0000\u0000\u0172\u0173\u0005d\u0000"+ + "\u0000\u0173\u0174\u0005r\u0000\u0000\u0174\u0175\u0005o\u0000\u0000\u0175"+ + "\u0176\u0005p\u0000\u0000\u0176\u0177\u0001\u0000\u0000\u0000\u0177\u0178"+ + "\u0006\u0001\u0001\u0000\u0178\u0011\u0001\u0000\u0000\u0000\u0179\u017a"+ + "\u0005e\u0000\u0000\u017a\u017b\u0005n\u0000\u0000\u017b\u017c\u0005r"+ + "\u0000\u0000\u017c\u017d\u0005i\u0000\u0000\u017d\u017e\u0005c\u0000\u0000"+ + "\u017e\u017f\u0005h\u0000\u0000\u017f\u0180\u0001\u0000\u0000\u0000\u0180"+ + "\u0181\u0006\u0002\u0002\u0000\u0181\u0013\u0001\u0000\u0000\u0000\u0182"+ + "\u0183\u0005e\u0000\u0000\u0183\u0184\u0005v\u0000\u0000\u0184\u0185\u0005"+ + "a\u0000\u0000\u0185\u0186\u0005l\u0000\u0000\u0186\u0187\u0001\u0000\u0000"+ + "\u0000\u0187\u0188\u0006\u0003\u0000\u0000\u0188\u0015\u0001\u0000\u0000"+ + "\u0000\u0189\u018a\u0005e\u0000\u0000\u018a\u018b\u0005x\u0000\u0000\u018b"+ + "\u018c\u0005p\u0000\u0000\u018c\u018d\u0005l\u0000\u0000\u018d\u018e\u0005"+ + "a\u0000\u0000\u018e\u018f\u0005i\u0000\u0000\u018f\u0190\u0005n\u0000"+ + "\u0000\u0190\u0191\u0001\u0000\u0000\u0000\u0191\u0192\u0006\u0004\u0003"+ + "\u0000\u0192\u0017\u0001\u0000\u0000\u0000\u0193\u0194\u0005f\u0000\u0000"+ + "\u0194\u0195\u0005r\u0000\u0000\u0195\u0196\u0005o\u0000\u0000\u0196\u0197"+ + "\u0005m\u0000\u0000\u0197\u0198\u0001\u0000\u0000\u0000\u0198\u0199\u0006"+ + "\u0005\u0004\u0000\u0199\u0019\u0001\u0000\u0000\u0000\u019a\u019b\u0005"+ + "g\u0000\u0000\u019b\u019c\u0005r\u0000\u0000\u019c\u019d\u0005o\u0000"+ + "\u0000\u019d\u019e\u0005k\u0000\u0000\u019e\u019f\u0001\u0000\u0000\u0000"+ + "\u019f\u01a0\u0006\u0006\u0000\u0000\u01a0\u001b\u0001\u0000\u0000\u0000"+ + "\u01a1\u01a2\u0005i\u0000\u0000\u01a2\u01a3\u0005n\u0000\u0000\u01a3\u01a4"+ + "\u0005l\u0000\u0000\u01a4\u01a5\u0005i\u0000\u0000\u01a5\u01a6\u0005n"+ + "\u0000\u0000\u01a6\u01a7\u0005e\u0000\u0000\u01a7\u01a8\u0005s\u0000\u0000"+ + "\u01a8\u01a9\u0005t\u0000\u0000\u01a9\u01aa\u0005a\u0000\u0000\u01aa\u01ab"+ + "\u0005t\u0000\u0000\u01ab\u01ac\u0005s\u0000\u0000\u01ac\u01ad\u0001\u0000"+ + "\u0000\u0000\u01ad\u01ae\u0006\u0007\u0000\u0000\u01ae\u001d\u0001\u0000"+ + "\u0000\u0000\u01af\u01b0\u0005k\u0000\u0000\u01b0\u01b1\u0005e\u0000\u0000"+ + "\u01b1\u01b2\u0005e\u0000\u0000\u01b2\u01b3\u0005p\u0000\u0000\u01b3\u01b4"+ + "\u0001\u0000\u0000\u0000\u01b4\u01b5\u0006\b\u0001\u0000\u01b5\u001f\u0001"+ + "\u0000\u0000\u0000\u01b6\u01b7\u0005l\u0000\u0000\u01b7\u01b8\u0005i\u0000"+ + "\u0000\u01b8\u01b9\u0005m\u0000\u0000\u01b9\u01ba\u0005i\u0000\u0000\u01ba"+ + "\u01bb\u0005t\u0000\u0000\u01bb\u01bc\u0001\u0000\u0000\u0000\u01bc\u01bd"+ + "\u0006\t\u0000\u0000\u01bd!\u0001\u0000\u0000\u0000\u01be\u01bf\u0005"+ + "m\u0000\u0000\u01bf\u01c0\u0005e\u0000\u0000\u01c0\u01c1\u0005t\u0000"+ + "\u0000\u01c1\u01c2\u0005a\u0000\u0000\u01c2\u01c3\u0001\u0000\u0000\u0000"+ + "\u01c3\u01c4\u0006\n\u0005\u0000\u01c4#\u0001\u0000\u0000\u0000\u01c5"+ + "\u01c6\u0005m\u0000\u0000\u01c6\u01c7\u0005e\u0000\u0000\u01c7\u01c8\u0005"+ + "t\u0000\u0000\u01c8\u01c9\u0005r\u0000\u0000\u01c9\u01ca\u0005i\u0000"+ + "\u0000\u01ca\u01cb\u0005c\u0000\u0000\u01cb\u01cc\u0005s\u0000\u0000\u01cc"+ + "\u01cd\u0001\u0000\u0000\u0000\u01cd\u01ce\u0006\u000b\u0006\u0000\u01ce"+ + "%\u0001\u0000\u0000\u0000\u01cf\u01d0\u0005m\u0000\u0000\u01d0\u01d1\u0005"+ + "v\u0000\u0000\u01d1\u01d2\u0005_\u0000\u0000\u01d2\u01d3\u0005e\u0000"+ + "\u0000\u01d3\u01d4\u0005x\u0000\u0000\u01d4\u01d5\u0005p\u0000\u0000\u01d5"+ + "\u01d6\u0005a\u0000\u0000\u01d6\u01d7\u0005n\u0000\u0000\u01d7\u01d8\u0005"+ + "d\u0000\u0000\u01d8\u01d9\u0001\u0000\u0000\u0000\u01d9\u01da\u0006\f"+ + "\u0007\u0000\u01da\'\u0001\u0000\u0000\u0000\u01db\u01dc\u0005r\u0000"+ + "\u0000\u01dc\u01dd\u0005e\u0000\u0000\u01dd\u01de\u0005n\u0000\u0000\u01de"+ + "\u01df\u0005a\u0000\u0000\u01df\u01e0\u0005m\u0000\u0000\u01e0\u01e1\u0005"+ + "e\u0000\u0000\u01e1\u01e2\u0001\u0000\u0000\u0000\u01e2\u01e3\u0006\r"+ + "\b\u0000\u01e3)\u0001\u0000\u0000\u0000\u01e4\u01e5\u0005r\u0000\u0000"+ + "\u01e5\u01e6\u0005o\u0000\u0000\u01e6\u01e7\u0005w\u0000\u0000\u01e7\u01e8"+ + "\u0001\u0000\u0000\u0000\u01e8\u01e9\u0006\u000e\u0000\u0000\u01e9+\u0001"+ + "\u0000\u0000\u0000\u01ea\u01eb\u0005s\u0000\u0000\u01eb\u01ec\u0005h\u0000"+ + "\u0000\u01ec\u01ed\u0005o\u0000\u0000\u01ed\u01ee\u0005w\u0000\u0000\u01ee"+ + "\u01ef\u0001\u0000\u0000\u0000\u01ef\u01f0\u0006\u000f\t\u0000\u01f0-"+ + "\u0001\u0000\u0000\u0000\u01f1\u01f2\u0005s\u0000\u0000\u01f2\u01f3\u0005"+ + "o\u0000\u0000\u01f3\u01f4\u0005r\u0000\u0000\u01f4\u01f5\u0005t\u0000"+ + "\u0000\u01f5\u01f6\u0001\u0000\u0000\u0000\u01f6\u01f7\u0006\u0010\u0000"+ + "\u0000\u01f7/\u0001\u0000\u0000\u0000\u01f8\u01f9\u0005s\u0000\u0000\u01f9"+ + "\u01fa\u0005t\u0000\u0000\u01fa\u01fb\u0005a\u0000\u0000\u01fb\u01fc\u0005"+ + "t\u0000\u0000\u01fc\u01fd\u0005s\u0000\u0000\u01fd\u01fe\u0001\u0000\u0000"+ + "\u0000\u01fe\u01ff\u0006\u0011\u0000\u0000\u01ff1\u0001\u0000\u0000\u0000"+ + "\u0200\u0201\u0005w\u0000\u0000\u0201\u0202\u0005h\u0000\u0000\u0202\u0203"+ + "\u0005e\u0000\u0000\u0203\u0204\u0005r\u0000\u0000\u0204\u0205\u0005e"+ + "\u0000\u0000\u0205\u0206\u0001\u0000\u0000\u0000\u0206\u0207\u0006\u0012"+ + "\u0000\u0000\u02073\u0001\u0000\u0000\u0000\u0208\u020a\b\u0000\u0000"+ + "\u0000\u0209\u0208\u0001\u0000\u0000\u0000\u020a\u020b\u0001\u0000\u0000"+ + "\u0000\u020b\u0209\u0001\u0000\u0000\u0000\u020b\u020c\u0001\u0000\u0000"+ + "\u0000\u020c\u020d\u0001\u0000\u0000\u0000\u020d\u020e\u0006\u0013\u0000"+ + "\u0000\u020e5\u0001\u0000\u0000\u0000\u020f\u0210\u0005/\u0000\u0000\u0210"+ + "\u0211\u0005/\u0000\u0000\u0211\u0215\u0001\u0000\u0000\u0000\u0212\u0214"+ + "\b\u0001\u0000\u0000\u0213\u0212\u0001\u0000\u0000\u0000\u0214\u0217\u0001"+ + "\u0000\u0000\u0000\u0215\u0213\u0001\u0000\u0000\u0000\u0215\u0216\u0001"+ + "\u0000\u0000\u0000\u0216\u0219\u0001\u0000\u0000\u0000\u0217\u0215\u0001"+ + "\u0000\u0000\u0000\u0218\u021a\u0005\r\u0000\u0000\u0219\u0218\u0001\u0000"+ + "\u0000\u0000\u0219\u021a\u0001\u0000\u0000\u0000\u021a\u021c\u0001\u0000"+ + "\u0000\u0000\u021b\u021d\u0005\n\u0000\u0000\u021c\u021b\u0001\u0000\u0000"+ + "\u0000\u021c\u021d\u0001\u0000\u0000\u0000\u021d\u021e\u0001\u0000\u0000"+ + "\u0000\u021e\u021f\u0006\u0014\n\u0000\u021f7\u0001\u0000\u0000\u0000"+ + "\u0220\u0221\u0005/\u0000\u0000\u0221\u0222\u0005*\u0000\u0000\u0222\u0227"+ + "\u0001\u0000\u0000\u0000\u0223\u0226\u00038\u0015\u0000\u0224\u0226\t"+ + "\u0000\u0000\u0000\u0225\u0223\u0001\u0000\u0000\u0000\u0225\u0224\u0001"+ + "\u0000\u0000\u0000\u0226\u0229\u0001\u0000\u0000\u0000\u0227\u0228\u0001"+ + "\u0000\u0000\u0000\u0227\u0225\u0001\u0000\u0000\u0000\u0228\u022a\u0001"+ + "\u0000\u0000\u0000\u0229\u0227\u0001\u0000\u0000\u0000\u022a\u022b\u0005"+ + "*\u0000\u0000\u022b\u022c\u0005/\u0000\u0000\u022c\u022d\u0001\u0000\u0000"+ + "\u0000\u022d\u022e\u0006\u0015\n\u0000\u022e9\u0001\u0000\u0000\u0000"+ + "\u022f\u0231\u0007\u0002\u0000\u0000\u0230\u022f\u0001\u0000\u0000\u0000"+ + "\u0231\u0232\u0001\u0000\u0000\u0000\u0232\u0230\u0001\u0000\u0000\u0000"+ + "\u0232\u0233\u0001\u0000\u0000\u0000\u0233\u0234\u0001\u0000\u0000\u0000"+ + "\u0234\u0235\u0006\u0016\n\u0000\u0235;\u0001\u0000\u0000\u0000\u0236"+ + "\u023a\b\u0003\u0000\u0000\u0237\u0238\u0005/\u0000\u0000\u0238\u023a"+ + "\b\u0004\u0000\u0000\u0239\u0236\u0001\u0000\u0000\u0000\u0239\u0237\u0001"+ + "\u0000\u0000\u0000\u023a=\u0001\u0000\u0000\u0000\u023b\u023d\u0003<\u0017"+ + "\u0000\u023c\u023b\u0001\u0000\u0000\u0000\u023d\u023e\u0001\u0000\u0000"+ + "\u0000\u023e\u023c\u0001\u0000\u0000\u0000\u023e\u023f\u0001\u0000\u0000"+ + "\u0000\u023f?\u0001\u0000\u0000\u0000\u0240\u0241\u0003\u00acO\u0000\u0241"+ + "\u0242\u0001\u0000\u0000\u0000\u0242\u0243\u0006\u0019\u000b\u0000\u0243"+ + "\u0244\u0006\u0019\f\u0000\u0244A\u0001\u0000\u0000\u0000\u0245\u0246"+ + "\u0003J\u001e\u0000\u0246\u0247\u0001\u0000\u0000\u0000\u0247\u0248\u0006"+ + "\u001a\r\u0000\u0248\u0249\u0006\u001a\u000e\u0000\u0249C\u0001\u0000"+ + "\u0000\u0000\u024a\u024b\u0003:\u0016\u0000\u024b\u024c\u0001\u0000\u0000"+ + "\u0000\u024c\u024d\u0006\u001b\n\u0000\u024dE\u0001\u0000\u0000\u0000"+ + "\u024e\u024f\u00036\u0014\u0000\u024f\u0250\u0001\u0000\u0000\u0000\u0250"+ + "\u0251\u0006\u001c\n\u0000\u0251G\u0001\u0000\u0000\u0000\u0252\u0253"+ + "\u00038\u0015\u0000\u0253\u0254\u0001\u0000\u0000\u0000\u0254\u0255\u0006"+ + "\u001d\n\u0000\u0255I\u0001\u0000\u0000\u0000\u0256\u0257\u0005|\u0000"+ + "\u0000\u0257\u0258\u0001\u0000\u0000\u0000\u0258\u0259\u0006\u001e\u000e"+ + "\u0000\u0259K\u0001\u0000\u0000\u0000\u025a\u025b\u0007\u0005\u0000\u0000"+ + "\u025bM\u0001\u0000\u0000\u0000\u025c\u025d\u0007\u0006\u0000\u0000\u025d"+ + "O\u0001\u0000\u0000\u0000\u025e\u025f\u0005\\\u0000\u0000\u025f\u0260"+ + "\u0007\u0007\u0000\u0000\u0260Q\u0001\u0000\u0000\u0000\u0261\u0262\b"+ + "\b\u0000\u0000\u0262S\u0001\u0000\u0000\u0000\u0263\u0265\u0007\t\u0000"+ + "\u0000\u0264\u0266\u0007\n\u0000\u0000\u0265\u0264\u0001\u0000\u0000\u0000"+ + "\u0265\u0266\u0001\u0000\u0000\u0000\u0266\u0268\u0001\u0000\u0000\u0000"+ + "\u0267\u0269\u0003L\u001f\u0000\u0268\u0267\u0001\u0000\u0000\u0000\u0269"+ + "\u026a\u0001\u0000\u0000\u0000\u026a\u0268\u0001\u0000\u0000\u0000\u026a"+ + "\u026b\u0001\u0000\u0000\u0000\u026bU\u0001\u0000\u0000\u0000\u026c\u026d"+ + "\u0005@\u0000\u0000\u026dW\u0001\u0000\u0000\u0000\u026e\u026f\u0005`"+ + "\u0000\u0000\u026fY\u0001\u0000\u0000\u0000\u0270\u0274\b\u000b\u0000"+ + "\u0000\u0271\u0272\u0005`\u0000\u0000\u0272\u0274\u0005`\u0000\u0000\u0273"+ + "\u0270\u0001\u0000\u0000\u0000\u0273\u0271\u0001\u0000\u0000\u0000\u0274"+ + "[\u0001\u0000\u0000\u0000\u0275\u0276\u0005_\u0000\u0000\u0276]\u0001"+ + "\u0000\u0000\u0000\u0277\u027b\u0003N \u0000\u0278\u027b\u0003L\u001f"+ + "\u0000\u0279\u027b\u0003\\\'\u0000\u027a\u0277\u0001\u0000\u0000\u0000"+ + "\u027a\u0278\u0001\u0000\u0000\u0000\u027a\u0279\u0001\u0000\u0000\u0000"+ + "\u027b_\u0001\u0000\u0000\u0000\u027c\u0281\u0005\"\u0000\u0000\u027d"+ + "\u0280\u0003P!\u0000\u027e\u0280\u0003R\"\u0000\u027f\u027d\u0001\u0000"+ + "\u0000\u0000\u027f\u027e\u0001\u0000\u0000\u0000\u0280\u0283\u0001\u0000"+ + "\u0000\u0000\u0281\u027f\u0001\u0000\u0000\u0000\u0281\u0282\u0001\u0000"+ + "\u0000\u0000\u0282\u0284\u0001\u0000\u0000\u0000\u0283\u0281\u0001\u0000"+ + "\u0000\u0000\u0284\u029a\u0005\"\u0000\u0000\u0285\u0286\u0005\"\u0000"+ + "\u0000\u0286\u0287\u0005\"\u0000\u0000\u0287\u0288\u0005\"\u0000\u0000"+ + "\u0288\u028c\u0001\u0000\u0000\u0000\u0289\u028b\b\u0001\u0000\u0000\u028a"+ + "\u0289\u0001\u0000\u0000\u0000\u028b\u028e\u0001\u0000\u0000\u0000\u028c"+ + "\u028d\u0001\u0000\u0000\u0000\u028c\u028a\u0001\u0000\u0000\u0000\u028d"+ "\u028f\u0001\u0000\u0000\u0000\u028e\u028c\u0001\u0000\u0000\u0000\u028f"+ - "\u0291\u0001\u0000\u0000\u0000\u0290\u028e\u0001\u0000\u0000\u0000\u0291"+ - "\u0292\u0005\"\u0000\u0000\u0292\u0293\u0005\"\u0000\u0000\u0293\u0294"+ - "\u0005\"\u0000\u0000\u0294\u0296\u0001\u0000\u0000\u0000\u0295\u0297\u0005"+ - "\"\u0000\u0000\u0296\u0295\u0001\u0000\u0000\u0000\u0296\u0297\u0001\u0000"+ - "\u0000\u0000\u0297\u0299\u0001\u0000\u0000\u0000\u0298\u029a\u0005\"\u0000"+ - "\u0000\u0299\u0298\u0001\u0000\u0000\u0000\u0299\u029a\u0001\u0000\u0000"+ - "\u0000\u029a\u029c\u0001\u0000\u0000\u0000\u029b\u027e\u0001\u0000\u0000"+ - "\u0000\u029b\u0287\u0001\u0000\u0000\u0000\u029ca\u0001\u0000\u0000\u0000"+ - "\u029d\u029f\u0003L\u001f\u0000\u029e\u029d\u0001\u0000\u0000\u0000\u029f"+ - "\u02a0\u0001\u0000\u0000\u0000\u02a0\u029e\u0001\u0000\u0000\u0000\u02a0"+ - "\u02a1\u0001\u0000\u0000\u0000\u02a1c\u0001\u0000\u0000\u0000\u02a2\u02a4"+ - "\u0003L\u001f\u0000\u02a3\u02a2\u0001\u0000\u0000\u0000\u02a4\u02a5\u0001"+ - "\u0000\u0000\u0000\u02a5\u02a3\u0001\u0000\u0000\u0000\u02a5\u02a6\u0001"+ - "\u0000\u0000\u0000\u02a6\u02a7\u0001\u0000\u0000\u0000\u02a7\u02ab\u0003"+ - "t3\u0000\u02a8\u02aa\u0003L\u001f\u0000\u02a9\u02a8\u0001\u0000\u0000"+ - "\u0000\u02aa\u02ad\u0001\u0000\u0000\u0000\u02ab\u02a9\u0001\u0000\u0000"+ - "\u0000\u02ab\u02ac\u0001\u0000\u0000\u0000\u02ac\u02cd\u0001\u0000\u0000"+ - "\u0000\u02ad\u02ab\u0001\u0000\u0000\u0000\u02ae\u02b0\u0003t3\u0000\u02af"+ - "\u02b1\u0003L\u001f\u0000\u02b0\u02af\u0001\u0000\u0000\u0000\u02b1\u02b2"+ - "\u0001\u0000\u0000\u0000\u02b2\u02b0\u0001\u0000\u0000\u0000\u02b2\u02b3"+ - "\u0001\u0000\u0000\u0000\u02b3\u02cd\u0001\u0000\u0000\u0000\u02b4\u02b6"+ - "\u0003L\u001f\u0000\u02b5\u02b4\u0001\u0000\u0000\u0000\u02b6\u02b7\u0001"+ - "\u0000\u0000\u0000\u02b7\u02b5\u0001\u0000\u0000\u0000\u02b7\u02b8\u0001"+ - "\u0000\u0000\u0000\u02b8\u02c0\u0001\u0000\u0000\u0000\u02b9\u02bd\u0003"+ - "t3\u0000\u02ba\u02bc\u0003L\u001f\u0000\u02bb\u02ba\u0001\u0000\u0000"+ - "\u0000\u02bc\u02bf\u0001\u0000\u0000\u0000\u02bd\u02bb\u0001\u0000\u0000"+ - "\u0000\u02bd\u02be\u0001\u0000\u0000\u0000\u02be\u02c1\u0001\u0000\u0000"+ - "\u0000\u02bf\u02bd\u0001\u0000\u0000\u0000\u02c0\u02b9\u0001\u0000\u0000"+ - "\u0000\u02c0\u02c1\u0001\u0000\u0000\u0000\u02c1\u02c2\u0001\u0000\u0000"+ - "\u0000\u02c2\u02c3\u0003T#\u0000\u02c3\u02cd\u0001\u0000\u0000\u0000\u02c4"+ - "\u02c6\u0003t3\u0000\u02c5\u02c7\u0003L\u001f\u0000\u02c6\u02c5\u0001"+ - "\u0000\u0000\u0000\u02c7\u02c8\u0001\u0000\u0000\u0000\u02c8\u02c6\u0001"+ - "\u0000\u0000\u0000\u02c8\u02c9\u0001\u0000\u0000\u0000\u02c9\u02ca\u0001"+ - "\u0000\u0000\u0000\u02ca\u02cb\u0003T#\u0000\u02cb\u02cd\u0001\u0000\u0000"+ - "\u0000\u02cc\u02a3\u0001\u0000\u0000\u0000\u02cc\u02ae\u0001\u0000\u0000"+ - "\u0000\u02cc\u02b5\u0001\u0000\u0000\u0000\u02cc\u02c4\u0001\u0000\u0000"+ - "\u0000\u02cde\u0001\u0000\u0000\u0000\u02ce\u02cf\u0005b\u0000\u0000\u02cf"+ - "\u02d0\u0005y\u0000\u0000\u02d0g\u0001\u0000\u0000\u0000\u02d1\u02d2\u0005"+ - "a\u0000\u0000\u02d2\u02d3\u0005n\u0000\u0000\u02d3\u02d4\u0005d\u0000"+ - "\u0000\u02d4i\u0001\u0000\u0000\u0000\u02d5\u02d6\u0005a\u0000\u0000\u02d6"+ - "\u02d7\u0005s\u0000\u0000\u02d7\u02d8\u0005c\u0000\u0000\u02d8k\u0001"+ - "\u0000\u0000\u0000\u02d9\u02da\u0005=\u0000\u0000\u02dam\u0001\u0000\u0000"+ - "\u0000\u02db\u02dc\u0005:\u0000\u0000\u02dc\u02dd\u0005:\u0000\u0000\u02dd"+ - "o\u0001\u0000\u0000\u0000\u02de\u02df\u0005,\u0000\u0000\u02dfq\u0001"+ - "\u0000\u0000\u0000\u02e0\u02e1\u0005d\u0000\u0000\u02e1\u02e2\u0005e\u0000"+ - "\u0000\u02e2\u02e3\u0005s\u0000\u0000\u02e3\u02e4\u0005c\u0000\u0000\u02e4"+ - "s\u0001\u0000\u0000\u0000\u02e5\u02e6\u0005.\u0000\u0000\u02e6u\u0001"+ - "\u0000\u0000\u0000\u02e7\u02e8\u0005f\u0000\u0000\u02e8\u02e9\u0005a\u0000"+ - "\u0000\u02e9\u02ea\u0005l\u0000\u0000\u02ea\u02eb\u0005s\u0000\u0000\u02eb"+ - "\u02ec\u0005e\u0000\u0000\u02ecw\u0001\u0000\u0000\u0000\u02ed\u02ee\u0005"+ - "f\u0000\u0000\u02ee\u02ef\u0005i\u0000\u0000\u02ef\u02f0\u0005r\u0000"+ - "\u0000\u02f0\u02f1\u0005s\u0000\u0000\u02f1\u02f2\u0005t\u0000\u0000\u02f2"+ - "y\u0001\u0000\u0000\u0000\u02f3\u02f4\u0005l\u0000\u0000\u02f4\u02f5\u0005"+ - "a\u0000\u0000\u02f5\u02f6\u0005s\u0000\u0000\u02f6\u02f7\u0005t\u0000"+ - "\u0000\u02f7{\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005(\u0000\u0000\u02f9"+ - "}\u0001\u0000\u0000\u0000\u02fa\u02fb\u0005i\u0000\u0000\u02fb\u02fc\u0005"+ - "n\u0000\u0000\u02fc\u007f\u0001\u0000\u0000\u0000\u02fd\u02fe\u0005i\u0000"+ - "\u0000\u02fe\u02ff\u0005s\u0000\u0000\u02ff\u0081\u0001\u0000\u0000\u0000"+ - "\u0300\u0301\u0005l\u0000\u0000\u0301\u0302\u0005i\u0000\u0000\u0302\u0303"+ - "\u0005k\u0000\u0000\u0303\u0304\u0005e\u0000\u0000\u0304\u0083\u0001\u0000"+ - "\u0000\u0000\u0305\u0306\u0005n\u0000\u0000\u0306\u0307\u0005o\u0000\u0000"+ - "\u0307\u0308\u0005t\u0000\u0000\u0308\u0085\u0001\u0000\u0000\u0000\u0309"+ - "\u030a\u0005n\u0000\u0000\u030a\u030b\u0005u\u0000\u0000\u030b\u030c\u0005"+ - "l\u0000\u0000\u030c\u030d\u0005l\u0000\u0000\u030d\u0087\u0001\u0000\u0000"+ - "\u0000\u030e\u030f\u0005n\u0000\u0000\u030f\u0310\u0005u\u0000\u0000\u0310"+ - "\u0311\u0005l\u0000\u0000\u0311\u0312\u0005l\u0000\u0000\u0312\u0313\u0005"+ - "s\u0000\u0000\u0313\u0089\u0001\u0000\u0000\u0000\u0314\u0315\u0005o\u0000"+ - "\u0000\u0315\u0316\u0005r\u0000\u0000\u0316\u008b\u0001\u0000\u0000\u0000"+ - "\u0317\u0318\u0005?\u0000\u0000\u0318\u008d\u0001\u0000\u0000\u0000\u0319"+ - "\u031a\u0005r\u0000\u0000\u031a\u031b\u0005l\u0000\u0000\u031b\u031c\u0005"+ - "i\u0000\u0000\u031c\u031d\u0005k\u0000\u0000\u031d\u031e\u0005e\u0000"+ - "\u0000\u031e\u008f\u0001\u0000\u0000\u0000\u031f\u0320\u0005)\u0000\u0000"+ - "\u0320\u0091\u0001\u0000\u0000\u0000\u0321\u0322\u0005t\u0000\u0000\u0322"+ - "\u0323\u0005r\u0000\u0000\u0323\u0324\u0005u\u0000\u0000\u0324\u0325\u0005"+ - "e\u0000\u0000\u0325\u0093\u0001\u0000\u0000\u0000\u0326\u0327\u0005=\u0000"+ - "\u0000\u0327\u0328\u0005=\u0000\u0000\u0328\u0095\u0001\u0000\u0000\u0000"+ - "\u0329\u032a\u0005=\u0000\u0000\u032a\u032b\u0005~\u0000\u0000\u032b\u0097"+ - "\u0001\u0000\u0000\u0000\u032c\u032d\u0005!\u0000\u0000\u032d\u032e\u0005"+ - "=\u0000\u0000\u032e\u0099\u0001\u0000\u0000\u0000\u032f\u0330\u0005<\u0000"+ - "\u0000\u0330\u009b\u0001\u0000\u0000\u0000\u0331\u0332\u0005<\u0000\u0000"+ - "\u0332\u0333\u0005=\u0000\u0000\u0333\u009d\u0001\u0000\u0000\u0000\u0334"+ - "\u0335\u0005>\u0000\u0000\u0335\u009f\u0001\u0000\u0000\u0000\u0336\u0337"+ - "\u0005>\u0000\u0000\u0337\u0338\u0005=\u0000\u0000\u0338\u00a1\u0001\u0000"+ - "\u0000\u0000\u0339\u033a\u0005+\u0000\u0000\u033a\u00a3\u0001\u0000\u0000"+ - "\u0000\u033b\u033c\u0005-\u0000\u0000\u033c\u00a5\u0001\u0000\u0000\u0000"+ - "\u033d\u033e\u0005*\u0000\u0000\u033e\u00a7\u0001\u0000\u0000\u0000\u033f"+ - "\u0340\u0005/\u0000\u0000\u0340\u00a9\u0001\u0000\u0000\u0000\u0341\u0342"+ - "\u0005%\u0000\u0000\u0342\u00ab\u0001\u0000\u0000\u0000\u0343\u0344\u0005"+ - "[\u0000\u0000\u0344\u0345\u0001\u0000\u0000\u0000\u0345\u0346\u0006O\u0000"+ - "\u0000\u0346\u0347\u0006O\u0000\u0000\u0347\u00ad\u0001\u0000\u0000\u0000"+ - "\u0348\u0349\u0005]\u0000\u0000\u0349\u034a\u0001\u0000\u0000\u0000\u034a"+ - "\u034b\u0006P\u000e\u0000\u034b\u034c\u0006P\u000e\u0000\u034c\u00af\u0001"+ - "\u0000\u0000\u0000\u034d\u0351\u0003N \u0000\u034e\u0350\u0003^(\u0000"+ - "\u034f\u034e\u0001\u0000\u0000\u0000\u0350\u0353\u0001\u0000\u0000\u0000"+ - "\u0351\u034f\u0001\u0000\u0000\u0000\u0351\u0352\u0001\u0000\u0000\u0000"+ - "\u0352\u035e\u0001\u0000\u0000\u0000\u0353\u0351\u0001\u0000\u0000\u0000"+ - "\u0354\u0357\u0003\\\'\u0000\u0355\u0357\u0003V$\u0000\u0356\u0354\u0001"+ - "\u0000\u0000\u0000\u0356\u0355\u0001\u0000\u0000\u0000\u0357\u0359\u0001"+ - "\u0000\u0000\u0000\u0358\u035a\u0003^(\u0000\u0359\u0358\u0001\u0000\u0000"+ - "\u0000\u035a\u035b\u0001\u0000\u0000\u0000\u035b\u0359\u0001\u0000\u0000"+ - "\u0000\u035b\u035c\u0001\u0000\u0000\u0000\u035c\u035e\u0001\u0000\u0000"+ - "\u0000\u035d\u034d\u0001\u0000\u0000\u0000\u035d\u0356\u0001\u0000\u0000"+ - "\u0000\u035e\u00b1\u0001\u0000\u0000\u0000\u035f\u0361\u0003X%\u0000\u0360"+ - "\u0362\u0003Z&\u0000\u0361\u0360\u0001\u0000\u0000\u0000\u0362\u0363\u0001"+ - "\u0000\u0000\u0000\u0363\u0361\u0001\u0000\u0000\u0000\u0363\u0364\u0001"+ - "\u0000\u0000\u0000\u0364\u0365\u0001\u0000\u0000\u0000\u0365\u0366\u0003"+ - "X%\u0000\u0366\u00b3\u0001\u0000\u0000\u0000\u0367\u0368\u0003\u00b2R"+ - "\u0000\u0368\u00b5\u0001\u0000\u0000\u0000\u0369\u036a\u00036\u0014\u0000"+ - "\u036a\u036b\u0001\u0000\u0000\u0000\u036b\u036c\u0006T\n\u0000\u036c"+ - "\u00b7\u0001\u0000\u0000\u0000\u036d\u036e\u00038\u0015\u0000\u036e\u036f"+ - "\u0001\u0000\u0000\u0000\u036f\u0370\u0006U\n\u0000\u0370\u00b9\u0001"+ - "\u0000\u0000\u0000\u0371\u0372\u0003:\u0016\u0000\u0372\u0373\u0001\u0000"+ - "\u0000\u0000\u0373\u0374\u0006V\n\u0000\u0374\u00bb\u0001\u0000\u0000"+ - "\u0000\u0375\u0376\u0003J\u001e\u0000\u0376\u0377\u0001\u0000\u0000\u0000"+ - "\u0377\u0378\u0006W\r\u0000\u0378\u0379\u0006W\u000e\u0000\u0379\u00bd"+ - "\u0001\u0000\u0000\u0000\u037a\u037b\u0003\u00acO\u0000\u037b\u037c\u0001"+ - "\u0000\u0000\u0000\u037c\u037d\u0006X\u000b\u0000\u037d\u00bf\u0001\u0000"+ - "\u0000\u0000\u037e\u037f\u0003\u00aeP\u0000\u037f\u0380\u0001\u0000\u0000"+ - "\u0000\u0380\u0381\u0006Y\u000f\u0000\u0381\u00c1\u0001\u0000\u0000\u0000"+ - "\u0382\u0383\u0003p1\u0000\u0383\u0384\u0001\u0000\u0000\u0000\u0384\u0385"+ - "\u0006Z\u0010\u0000\u0385\u00c3\u0001\u0000\u0000\u0000\u0386\u0387\u0003"+ - "l/\u0000\u0387\u0388\u0001\u0000\u0000\u0000\u0388\u0389\u0006[\u0011"+ - "\u0000\u0389\u00c5\u0001\u0000\u0000\u0000\u038a\u038b\u0003`)\u0000\u038b"+ - "\u038c\u0001\u0000\u0000\u0000\u038c\u038d\u0006\\\u0012\u0000\u038d\u00c7"+ - "\u0001\u0000\u0000\u0000\u038e\u038f\u0005o\u0000\u0000\u038f\u0390\u0005"+ - "p\u0000\u0000\u0390\u0391\u0005t\u0000\u0000\u0391\u0392\u0005i\u0000"+ - "\u0000\u0392\u0393\u0005o\u0000\u0000\u0393\u0394\u0005n\u0000\u0000\u0394"+ - "\u0395\u0005s\u0000\u0000\u0395\u00c9\u0001\u0000\u0000\u0000\u0396\u0397"+ - "\u0005m\u0000\u0000\u0397\u0398\u0005e\u0000\u0000\u0398\u0399\u0005t"+ - "\u0000\u0000\u0399\u039a\u0005a\u0000\u0000\u039a\u039b\u0005d\u0000\u0000"+ - "\u039b\u039c\u0005a\u0000\u0000\u039c\u039d\u0005t\u0000\u0000\u039d\u039e"+ - "\u0005a\u0000\u0000\u039e\u00cb\u0001\u0000\u0000\u0000\u039f\u03a0\u0003"+ - ">\u0018\u0000\u03a0\u03a1\u0001\u0000\u0000\u0000\u03a1\u03a2\u0006_\u0013"+ - "\u0000\u03a2\u00cd\u0001\u0000\u0000\u0000\u03a3\u03a4\u00036\u0014\u0000"+ - "\u03a4\u03a5\u0001\u0000\u0000\u0000\u03a5\u03a6\u0006`\n\u0000\u03a6"+ - "\u00cf\u0001\u0000\u0000\u0000\u03a7\u03a8\u00038\u0015\u0000\u03a8\u03a9"+ - "\u0001\u0000\u0000\u0000\u03a9\u03aa\u0006a\n\u0000\u03aa\u00d1\u0001"+ - "\u0000\u0000\u0000\u03ab\u03ac\u0003:\u0016\u0000\u03ac\u03ad\u0001\u0000"+ - "\u0000\u0000\u03ad\u03ae\u0006b\n\u0000\u03ae\u00d3\u0001\u0000\u0000"+ - "\u0000\u03af\u03b0\u0003J\u001e\u0000\u03b0\u03b1\u0001\u0000\u0000\u0000"+ - "\u03b1\u03b2\u0006c\r\u0000\u03b2\u03b3\u0006c\u000e\u0000\u03b3\u00d5"+ - "\u0001\u0000\u0000\u0000\u03b4\u03b5\u0003t3\u0000\u03b5\u03b6\u0001\u0000"+ - "\u0000\u0000\u03b6\u03b7\u0006d\u0014\u0000\u03b7\u00d7\u0001\u0000\u0000"+ - "\u0000\u03b8\u03b9\u0003p1\u0000\u03b9\u03ba\u0001\u0000\u0000\u0000\u03ba"+ - "\u03bb\u0006e\u0010\u0000\u03bb\u00d9\u0001\u0000\u0000\u0000\u03bc\u03c1"+ - "\u0003N \u0000\u03bd\u03c1\u0003L\u001f\u0000\u03be\u03c1\u0003\\\'\u0000"+ - "\u03bf\u03c1\u0003\u00a6L\u0000\u03c0\u03bc\u0001\u0000\u0000\u0000\u03c0"+ - "\u03bd\u0001\u0000\u0000\u0000\u03c0\u03be\u0001\u0000\u0000\u0000\u03c0"+ - "\u03bf\u0001\u0000\u0000\u0000\u03c1\u00db\u0001\u0000\u0000\u0000\u03c2"+ - "\u03c5\u0003N \u0000\u03c3\u03c5\u0003\u00a6L\u0000\u03c4\u03c2\u0001"+ - "\u0000\u0000\u0000\u03c4\u03c3\u0001\u0000\u0000\u0000\u03c5\u03c9\u0001"+ - "\u0000\u0000\u0000\u03c6\u03c8\u0003\u00daf\u0000\u03c7\u03c6\u0001\u0000"+ - "\u0000\u0000\u03c8\u03cb\u0001\u0000\u0000\u0000\u03c9\u03c7\u0001\u0000"+ - "\u0000\u0000\u03c9\u03ca\u0001\u0000\u0000\u0000\u03ca\u03d6\u0001\u0000"+ - "\u0000\u0000\u03cb\u03c9\u0001\u0000\u0000\u0000\u03cc\u03cf\u0003\\\'"+ - "\u0000\u03cd\u03cf\u0003V$\u0000\u03ce\u03cc\u0001\u0000\u0000\u0000\u03ce"+ - "\u03cd\u0001\u0000\u0000\u0000\u03cf\u03d1\u0001\u0000\u0000\u0000\u03d0"+ - "\u03d2\u0003\u00daf\u0000\u03d1\u03d0\u0001\u0000\u0000\u0000\u03d2\u03d3"+ - "\u0001\u0000\u0000\u0000\u03d3\u03d1\u0001\u0000\u0000\u0000\u03d3\u03d4"+ - "\u0001\u0000\u0000\u0000\u03d4\u03d6\u0001\u0000\u0000\u0000\u03d5\u03c4"+ - "\u0001\u0000\u0000\u0000\u03d5\u03ce\u0001\u0000\u0000\u0000\u03d6\u00dd"+ - "\u0001\u0000\u0000\u0000\u03d7\u03da\u0003\u00dcg\u0000\u03d8\u03da\u0003"+ - "\u00b2R\u0000\u03d9\u03d7\u0001\u0000\u0000\u0000\u03d9\u03d8\u0001\u0000"+ - "\u0000\u0000\u03da\u03db\u0001\u0000\u0000\u0000\u03db\u03d9\u0001\u0000"+ - "\u0000\u0000\u03db\u03dc\u0001\u0000\u0000\u0000\u03dc\u00df\u0001\u0000"+ - "\u0000\u0000\u03dd\u03de\u00036\u0014\u0000\u03de\u03df\u0001\u0000\u0000"+ - "\u0000\u03df\u03e0\u0006i\n\u0000\u03e0\u00e1\u0001\u0000\u0000\u0000"+ - "\u03e1\u03e2\u00038\u0015\u0000\u03e2\u03e3\u0001\u0000\u0000\u0000\u03e3"+ - "\u03e4\u0006j\n\u0000\u03e4\u00e3\u0001\u0000\u0000\u0000\u03e5\u03e6"+ - "\u0003:\u0016\u0000\u03e6\u03e7\u0001\u0000\u0000\u0000\u03e7\u03e8\u0006"+ - "k\n\u0000\u03e8\u00e5\u0001\u0000\u0000\u0000\u03e9\u03ea\u0003J\u001e"+ - "\u0000\u03ea\u03eb\u0001\u0000\u0000\u0000\u03eb\u03ec\u0006l\r\u0000"+ - "\u03ec\u03ed\u0006l\u000e\u0000\u03ed\u00e7\u0001\u0000\u0000\u0000\u03ee"+ - "\u03ef\u0003l/\u0000\u03ef\u03f0\u0001\u0000\u0000\u0000\u03f0\u03f1\u0006"+ - "m\u0011\u0000\u03f1\u00e9\u0001\u0000\u0000\u0000\u03f2\u03f3\u0003p1"+ - "\u0000\u03f3\u03f4\u0001\u0000\u0000\u0000\u03f4\u03f5\u0006n\u0010\u0000"+ - "\u03f5\u00eb\u0001\u0000\u0000\u0000\u03f6\u03f7\u0003t3\u0000\u03f7\u03f8"+ - "\u0001\u0000\u0000\u0000\u03f8\u03f9\u0006o\u0014\u0000\u03f9\u00ed\u0001"+ - "\u0000\u0000\u0000\u03fa\u03fb\u0005a\u0000\u0000\u03fb\u03fc\u0005s\u0000"+ - "\u0000\u03fc\u00ef\u0001\u0000\u0000\u0000\u03fd\u03fe\u0003\u00deh\u0000"+ - "\u03fe\u03ff\u0001\u0000\u0000\u0000\u03ff\u0400\u0006q\u0015\u0000\u0400"+ - "\u00f1\u0001\u0000\u0000\u0000\u0401\u0402\u00036\u0014\u0000\u0402\u0403"+ - "\u0001\u0000\u0000\u0000\u0403\u0404\u0006r\n\u0000\u0404\u00f3\u0001"+ - "\u0000\u0000\u0000\u0405\u0406\u00038\u0015\u0000\u0406\u0407\u0001\u0000"+ - "\u0000\u0000\u0407\u0408\u0006s\n\u0000\u0408\u00f5\u0001\u0000\u0000"+ - "\u0000\u0409\u040a\u0003:\u0016\u0000\u040a\u040b\u0001\u0000\u0000\u0000"+ - "\u040b\u040c\u0006t\n\u0000\u040c\u00f7\u0001\u0000\u0000\u0000\u040d"+ - "\u040e\u0003J\u001e\u0000\u040e\u040f\u0001\u0000\u0000\u0000\u040f\u0410"+ - "\u0006u\r\u0000\u0410\u0411\u0006u\u000e\u0000\u0411\u00f9\u0001\u0000"+ - "\u0000\u0000\u0412\u0413\u0003\u00acO\u0000\u0413\u0414\u0001\u0000\u0000"+ - "\u0000\u0414\u0415\u0006v\u000b\u0000\u0415\u0416\u0006v\u0016\u0000\u0416"+ - "\u00fb\u0001\u0000\u0000\u0000\u0417\u0418\u0005o\u0000\u0000\u0418\u0419"+ - "\u0005n\u0000\u0000\u0419\u041a\u0001\u0000\u0000\u0000\u041a\u041b\u0006"+ - "w\u0017\u0000\u041b\u00fd\u0001\u0000\u0000\u0000\u041c\u041d\u0005w\u0000"+ - "\u0000\u041d\u041e\u0005i\u0000\u0000\u041e\u041f\u0005t\u0000\u0000\u041f"+ - "\u0420\u0005h\u0000\u0000\u0420\u0421\u0001\u0000\u0000\u0000\u0421\u0422"+ - "\u0006x\u0017\u0000\u0422\u00ff\u0001\u0000\u0000\u0000\u0423\u0424\b"+ - "\f\u0000\u0000\u0424\u0101\u0001\u0000\u0000\u0000\u0425\u0427\u0003\u0100"+ - "y\u0000\u0426\u0425\u0001\u0000\u0000\u0000\u0427\u0428\u0001\u0000\u0000"+ - "\u0000\u0428\u0426\u0001\u0000\u0000\u0000\u0428\u0429\u0001\u0000\u0000"+ - "\u0000\u0429\u042a\u0001\u0000\u0000\u0000\u042a\u042b\u0003\u0146\u009c"+ - "\u0000\u042b\u042d\u0001\u0000\u0000\u0000\u042c\u0426\u0001\u0000\u0000"+ - "\u0000\u042c\u042d\u0001\u0000\u0000\u0000\u042d\u042f\u0001\u0000\u0000"+ - "\u0000\u042e\u0430\u0003\u0100y\u0000\u042f\u042e\u0001\u0000\u0000\u0000"+ - "\u0430\u0431\u0001\u0000\u0000\u0000\u0431\u042f\u0001\u0000\u0000\u0000"+ - "\u0431\u0432\u0001\u0000\u0000\u0000\u0432\u0103\u0001\u0000\u0000\u0000"+ - "\u0433\u0434\u0003\u00b4S\u0000\u0434\u0435\u0001\u0000\u0000\u0000\u0435"+ - "\u0436\u0006{\u0018\u0000\u0436\u0105\u0001\u0000\u0000\u0000\u0437\u0438"+ - "\u0003\u0102z\u0000\u0438\u0439\u0001\u0000\u0000\u0000\u0439\u043a\u0006"+ - "|\u0019\u0000\u043a\u0107\u0001\u0000\u0000\u0000\u043b\u043c\u00036\u0014"+ - "\u0000\u043c\u043d\u0001\u0000\u0000\u0000\u043d\u043e\u0006}\n\u0000"+ - "\u043e\u0109\u0001\u0000\u0000\u0000\u043f\u0440\u00038\u0015\u0000\u0440"+ - "\u0441\u0001\u0000\u0000\u0000\u0441\u0442\u0006~\n\u0000\u0442\u010b"+ - "\u0001\u0000\u0000\u0000\u0443\u0444\u0003:\u0016\u0000\u0444\u0445\u0001"+ - "\u0000\u0000\u0000\u0445\u0446\u0006\u007f\n\u0000\u0446\u010d\u0001\u0000"+ - "\u0000\u0000\u0447\u0448\u0003J\u001e\u0000\u0448\u0449\u0001\u0000\u0000"+ - "\u0000\u0449\u044a\u0006\u0080\r\u0000\u044a\u044b\u0006\u0080\u000e\u0000"+ - "\u044b\u044c\u0006\u0080\u000e\u0000\u044c\u010f\u0001\u0000\u0000\u0000"+ - "\u044d\u044e\u0003l/\u0000\u044e\u044f\u0001\u0000\u0000\u0000\u044f\u0450"+ - "\u0006\u0081\u0011\u0000\u0450\u0111\u0001\u0000\u0000\u0000\u0451\u0452"+ - "\u0003p1\u0000\u0452\u0453\u0001\u0000\u0000\u0000\u0453\u0454\u0006\u0082"+ - "\u0010\u0000\u0454\u0113\u0001\u0000\u0000\u0000\u0455\u0456\u0003t3\u0000"+ - "\u0456\u0457\u0001\u0000\u0000\u0000\u0457\u0458\u0006\u0083\u0014\u0000"+ - "\u0458\u0115\u0001\u0000\u0000\u0000\u0459\u045a\u0003\u00fex\u0000\u045a"+ - "\u045b\u0001\u0000\u0000\u0000\u045b\u045c\u0006\u0084\u001a\u0000\u045c"+ - "\u0117\u0001\u0000\u0000\u0000\u045d\u045e\u0003\u00deh\u0000\u045e\u045f"+ - "\u0001\u0000\u0000\u0000\u045f\u0460\u0006\u0085\u0015\u0000\u0460\u0119"+ - "\u0001\u0000\u0000\u0000\u0461\u0462\u0003\u00b4S\u0000\u0462\u0463\u0001"+ - "\u0000\u0000\u0000\u0463\u0464\u0006\u0086\u0018\u0000\u0464\u011b\u0001"+ - "\u0000\u0000\u0000\u0465\u0466\u00036\u0014\u0000\u0466\u0467\u0001\u0000"+ - "\u0000\u0000\u0467\u0468\u0006\u0087\n\u0000\u0468\u011d\u0001\u0000\u0000"+ - "\u0000\u0469\u046a\u00038\u0015\u0000\u046a\u046b\u0001\u0000\u0000\u0000"+ - "\u046b\u046c\u0006\u0088\n\u0000\u046c\u011f\u0001\u0000\u0000\u0000\u046d"+ - "\u046e\u0003:\u0016\u0000\u046e\u046f\u0001\u0000\u0000\u0000\u046f\u0470"+ - "\u0006\u0089\n\u0000\u0470\u0121\u0001\u0000\u0000\u0000\u0471\u0472\u0003"+ - "J\u001e\u0000\u0472\u0473\u0001\u0000\u0000\u0000\u0473\u0474\u0006\u008a"+ - "\r\u0000\u0474\u0475\u0006\u008a\u000e\u0000\u0475\u0123\u0001\u0000\u0000"+ - "\u0000\u0476\u0477\u0003t3\u0000\u0477\u0478\u0001\u0000\u0000\u0000\u0478"+ - "\u0479\u0006\u008b\u0014\u0000\u0479\u0125\u0001\u0000\u0000\u0000\u047a"+ - "\u047b\u0003\u00b4S\u0000\u047b\u047c\u0001\u0000\u0000\u0000\u047c\u047d"+ - "\u0006\u008c\u0018\u0000\u047d\u0127\u0001\u0000\u0000\u0000\u047e\u047f"+ - "\u0003\u00b0Q\u0000\u047f\u0480\u0001\u0000\u0000\u0000\u0480\u0481\u0006"+ - "\u008d\u001b\u0000\u0481\u0129\u0001\u0000\u0000\u0000\u0482\u0483\u0003"+ - "6\u0014\u0000\u0483\u0484\u0001\u0000\u0000\u0000\u0484\u0485\u0006\u008e"+ - "\n\u0000\u0485\u012b\u0001\u0000\u0000\u0000\u0486\u0487\u00038\u0015"+ - "\u0000\u0487\u0488\u0001\u0000\u0000\u0000\u0488\u0489\u0006\u008f\n\u0000"+ - "\u0489\u012d\u0001\u0000\u0000\u0000\u048a\u048b\u0003:\u0016\u0000\u048b"+ - "\u048c\u0001\u0000\u0000\u0000\u048c\u048d\u0006\u0090\n\u0000\u048d\u012f"+ - "\u0001\u0000\u0000\u0000\u048e\u048f\u0003J\u001e\u0000\u048f\u0490\u0001"+ - "\u0000\u0000\u0000\u0490\u0491\u0006\u0091\r\u0000\u0491\u0492\u0006\u0091"+ - "\u000e\u0000\u0492\u0131\u0001\u0000\u0000\u0000\u0493\u0494\u0005i\u0000"+ - "\u0000\u0494\u0495\u0005n\u0000\u0000\u0495\u0496\u0005f\u0000\u0000\u0496"+ - "\u0497\u0005o\u0000\u0000\u0497\u0133\u0001\u0000\u0000\u0000\u0498\u0499"+ - "\u00036\u0014\u0000\u0499\u049a\u0001\u0000\u0000\u0000\u049a\u049b\u0006"+ - "\u0093\n\u0000\u049b\u0135\u0001\u0000\u0000\u0000\u049c\u049d\u00038"+ - "\u0015\u0000\u049d\u049e\u0001\u0000\u0000\u0000\u049e\u049f\u0006\u0094"+ - "\n\u0000\u049f\u0137\u0001\u0000\u0000\u0000\u04a0\u04a1\u0003:\u0016"+ - "\u0000\u04a1\u04a2\u0001\u0000\u0000\u0000\u04a2\u04a3\u0006\u0095\n\u0000"+ - "\u04a3\u0139\u0001\u0000\u0000\u0000\u04a4\u04a5\u0003J\u001e\u0000\u04a5"+ - "\u04a6\u0001\u0000\u0000\u0000\u04a6\u04a7\u0006\u0096\r\u0000\u04a7\u04a8"+ - "\u0006\u0096\u000e\u0000\u04a8\u013b\u0001\u0000\u0000\u0000\u04a9\u04aa"+ - "\u0005f\u0000\u0000\u04aa\u04ab\u0005u\u0000\u0000\u04ab\u04ac\u0005n"+ - "\u0000\u0000\u04ac\u04ad\u0005c\u0000\u0000\u04ad\u04ae\u0005t\u0000\u0000"+ - "\u04ae\u04af\u0005i\u0000\u0000\u04af\u04b0\u0005o\u0000\u0000\u04b0\u04b1"+ - "\u0005n\u0000\u0000\u04b1\u04b2\u0005s\u0000\u0000\u04b2\u013d\u0001\u0000"+ - "\u0000\u0000\u04b3\u04b4\u00036\u0014\u0000\u04b4\u04b5\u0001\u0000\u0000"+ - "\u0000\u04b5\u04b6\u0006\u0098\n\u0000\u04b6\u013f\u0001\u0000\u0000\u0000"+ - "\u04b7\u04b8\u00038\u0015\u0000\u04b8\u04b9\u0001\u0000\u0000\u0000\u04b9"+ - "\u04ba\u0006\u0099\n\u0000\u04ba\u0141\u0001\u0000\u0000\u0000\u04bb\u04bc"+ - "\u0003:\u0016\u0000\u04bc\u04bd\u0001\u0000\u0000\u0000\u04bd\u04be\u0006"+ - "\u009a\n\u0000\u04be\u0143\u0001\u0000\u0000\u0000\u04bf\u04c0\u0003\u00ae"+ - "P\u0000\u04c0\u04c1\u0001\u0000\u0000\u0000\u04c1\u04c2\u0006\u009b\u000f"+ - "\u0000\u04c2\u04c3\u0006\u009b\u000e\u0000\u04c3\u0145\u0001\u0000\u0000"+ - "\u0000\u04c4\u04c5\u0005:\u0000\u0000\u04c5\u0147\u0001\u0000\u0000\u0000"+ - "\u04c6\u04cc\u0003V$\u0000\u04c7\u04cc\u0003L\u001f\u0000\u04c8\u04cc"+ - "\u0003t3\u0000\u04c9\u04cc\u0003N \u0000\u04ca\u04cc\u0003\\\'\u0000\u04cb"+ - "\u04c6\u0001\u0000\u0000\u0000\u04cb\u04c7\u0001\u0000\u0000\u0000\u04cb"+ - "\u04c8\u0001\u0000\u0000\u0000\u04cb\u04c9\u0001\u0000\u0000\u0000\u04cb"+ - "\u04ca\u0001\u0000\u0000\u0000\u04cc\u04cd\u0001\u0000\u0000\u0000\u04cd"+ - "\u04cb\u0001\u0000\u0000\u0000\u04cd\u04ce\u0001\u0000\u0000\u0000\u04ce"+ - "\u0149\u0001\u0000\u0000\u0000\u04cf\u04d0\u00036\u0014\u0000\u04d0\u04d1"+ - "\u0001\u0000\u0000\u0000\u04d1\u04d2\u0006\u009e\n\u0000\u04d2\u014b\u0001"+ - "\u0000\u0000\u0000\u04d3\u04d4\u00038\u0015\u0000\u04d4\u04d5\u0001\u0000"+ - "\u0000\u0000\u04d5\u04d6\u0006\u009f\n\u0000\u04d6\u014d\u0001\u0000\u0000"+ - "\u0000\u04d7\u04d8\u0003:\u0016\u0000\u04d8\u04d9\u0001\u0000\u0000\u0000"+ - "\u04d9\u04da\u0006\u00a0\n\u0000\u04da\u014f\u0001\u0000\u0000\u0000\u04db"+ - "\u04dc\u0003J\u001e\u0000\u04dc\u04dd\u0001\u0000\u0000\u0000\u04dd\u04de"+ - "\u0006\u00a1\r\u0000\u04de\u04df\u0006\u00a1\u000e\u0000\u04df\u0151\u0001"+ - "\u0000\u0000\u0000\u04e0\u04e1\u0003>\u0018\u0000\u04e1\u04e2\u0001\u0000"+ - "\u0000\u0000\u04e2\u04e3\u0006\u00a2\u0013\u0000\u04e3\u04e4\u0006\u00a2"+ - "\u000e\u0000\u04e4\u04e5\u0006\u00a2\u001c\u0000\u04e5\u0153\u0001\u0000"+ - "\u0000\u0000\u04e6\u04e7\u00036\u0014\u0000\u04e7\u04e8\u0001\u0000\u0000"+ - "\u0000\u04e8\u04e9\u0006\u00a3\n\u0000\u04e9\u0155\u0001\u0000\u0000\u0000"+ - "\u04ea\u04eb\u00038\u0015\u0000\u04eb\u04ec\u0001\u0000\u0000\u0000\u04ec"+ - "\u04ed\u0006\u00a4\n\u0000\u04ed\u0157\u0001\u0000\u0000\u0000\u04ee\u04ef"+ - "\u0003:\u0016\u0000\u04ef\u04f0\u0001\u0000\u0000\u0000\u04f0\u04f1\u0006"+ - "\u00a5\n\u0000\u04f1\u0159\u0001\u0000\u0000\u0000\u04f2\u04f3\u0003p"+ - "1\u0000\u04f3\u04f4\u0001\u0000\u0000\u0000\u04f4\u04f5\u0006\u00a6\u0010"+ - "\u0000\u04f5\u04f6\u0006\u00a6\u000e\u0000\u04f6\u04f7\u0006\u00a6\u0006"+ - "\u0000\u04f7\u015b\u0001\u0000\u0000\u0000\u04f8\u04f9\u00036\u0014\u0000"+ - "\u04f9\u04fa\u0001\u0000\u0000\u0000\u04fa\u04fb\u0006\u00a7\n\u0000\u04fb"+ - "\u015d\u0001\u0000\u0000\u0000\u04fc\u04fd\u00038\u0015\u0000\u04fd\u04fe"+ - "\u0001\u0000\u0000\u0000\u04fe\u04ff\u0006\u00a8\n\u0000\u04ff\u015f\u0001"+ - "\u0000\u0000\u0000\u0500\u0501\u0003:\u0016\u0000\u0501\u0502\u0001\u0000"+ - "\u0000\u0000\u0502\u0503\u0006\u00a9\n\u0000\u0503\u0161\u0001\u0000\u0000"+ - "\u0000\u0504\u0505\u0003\u00b4S\u0000\u0505\u0506\u0001\u0000\u0000\u0000"+ - "\u0506\u0507\u0006\u00aa\u000e\u0000\u0507\u0508\u0006\u00aa\u0000\u0000"+ - "\u0508\u0509\u0006\u00aa\u0018\u0000\u0509\u0163\u0001\u0000\u0000\u0000"+ - "\u050a\u050b\u0003\u00b0Q\u0000\u050b\u050c\u0001\u0000\u0000\u0000\u050c"+ - "\u050d\u0006\u00ab\u000e\u0000\u050d\u050e\u0006\u00ab\u0000\u0000\u050e"+ - "\u050f\u0006\u00ab\u001b\u0000\u050f\u0165\u0001\u0000\u0000\u0000\u0510"+ - "\u0511\u0003f,\u0000\u0511\u0512\u0001\u0000\u0000\u0000\u0512\u0513\u0006"+ - "\u00ac\u000e\u0000\u0513\u0514\u0006\u00ac\u0000\u0000\u0514\u0515\u0006"+ - "\u00ac\u001d\u0000\u0515\u0167\u0001\u0000\u0000\u0000\u0516\u0517\u0003"+ - "J\u001e\u0000\u0517\u0518\u0001\u0000\u0000\u0000\u0518\u0519\u0006\u00ad"+ - "\r\u0000\u0519\u051a\u0006\u00ad\u000e\u0000\u051a\u0169\u0001\u0000\u0000"+ - "\u0000<\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f"+ - "\r\u020d\u0217\u021b\u021e\u0227\u0229\u0234\u023b\u0240\u0267\u026c\u0275"+ - "\u027c\u0281\u0283\u028e\u0296\u0299\u029b\u02a0\u02a5\u02ab\u02b2\u02b7"+ - "\u02bd\u02c0\u02c8\u02cc\u0351\u0356\u035b\u035d\u0363\u03c0\u03c4\u03c9"+ - "\u03ce\u03d3\u03d5\u03d9\u03db\u0428\u042c\u0431\u04cb\u04cd\u001e\u0005"+ - "\u0002\u0000\u0005\u0004\u0000\u0005\u0006\u0000\u0005\u0001\u0000\u0005"+ - "\u0003\u0000\u0005\n\u0000\u0005\f\u0000\u0005\b\u0000\u0005\u0005\u0000"+ - "\u0005\t\u0000\u0000\u0001\u0000\u0007C\u0000\u0005\u0000\u0000\u0007"+ - "\u001c\u0000\u0004\u0000\u0000\u0007D\u0000\u0007%\u0000\u0007#\u0000"+ - "\u0007\u001d\u0000\u0007\u0018\u0000\u0007\'\u0000\u0007O\u0000\u0005"+ - "\u000b\u0000\u0005\u0007\u0000\u0007F\u0000\u0007Y\u0000\u0007X\u0000"+ - "\u0007E\u0000\u0005\r\u0000\u0007 \u0000"; + "\u0290\u0005\"\u0000\u0000\u0290\u0291\u0005\"\u0000\u0000\u0291\u0292"+ + "\u0005\"\u0000\u0000\u0292\u0294\u0001\u0000\u0000\u0000\u0293\u0295\u0005"+ + "\"\u0000\u0000\u0294\u0293\u0001\u0000\u0000\u0000\u0294\u0295\u0001\u0000"+ + "\u0000\u0000\u0295\u0297\u0001\u0000\u0000\u0000\u0296\u0298\u0005\"\u0000"+ + "\u0000\u0297\u0296\u0001\u0000\u0000\u0000\u0297\u0298\u0001\u0000\u0000"+ + "\u0000\u0298\u029a\u0001\u0000\u0000\u0000\u0299\u027c\u0001\u0000\u0000"+ + "\u0000\u0299\u0285\u0001\u0000\u0000\u0000\u029aa\u0001\u0000\u0000\u0000"+ + "\u029b\u029d\u0003L\u001f\u0000\u029c\u029b\u0001\u0000\u0000\u0000\u029d"+ + "\u029e\u0001\u0000\u0000\u0000\u029e\u029c\u0001\u0000\u0000\u0000\u029e"+ + "\u029f\u0001\u0000\u0000\u0000\u029fc\u0001\u0000\u0000\u0000\u02a0\u02a2"+ + "\u0003L\u001f\u0000\u02a1\u02a0\u0001\u0000\u0000\u0000\u02a2\u02a3\u0001"+ + "\u0000\u0000\u0000\u02a3\u02a1\u0001\u0000\u0000\u0000\u02a3\u02a4\u0001"+ + "\u0000\u0000\u0000\u02a4\u02a5\u0001\u0000\u0000\u0000\u02a5\u02a9\u0003"+ + "t3\u0000\u02a6\u02a8\u0003L\u001f\u0000\u02a7\u02a6\u0001\u0000\u0000"+ + "\u0000\u02a8\u02ab\u0001\u0000\u0000\u0000\u02a9\u02a7\u0001\u0000\u0000"+ + "\u0000\u02a9\u02aa\u0001\u0000\u0000\u0000\u02aa\u02cb\u0001\u0000\u0000"+ + "\u0000\u02ab\u02a9\u0001\u0000\u0000\u0000\u02ac\u02ae\u0003t3\u0000\u02ad"+ + "\u02af\u0003L\u001f\u0000\u02ae\u02ad\u0001\u0000\u0000\u0000\u02af\u02b0"+ + "\u0001\u0000\u0000\u0000\u02b0\u02ae\u0001\u0000\u0000\u0000\u02b0\u02b1"+ + "\u0001\u0000\u0000\u0000\u02b1\u02cb\u0001\u0000\u0000\u0000\u02b2\u02b4"+ + "\u0003L\u001f\u0000\u02b3\u02b2\u0001\u0000\u0000\u0000\u02b4\u02b5\u0001"+ + "\u0000\u0000\u0000\u02b5\u02b3\u0001\u0000\u0000\u0000\u02b5\u02b6\u0001"+ + "\u0000\u0000\u0000\u02b6\u02be\u0001\u0000\u0000\u0000\u02b7\u02bb\u0003"+ + "t3\u0000\u02b8\u02ba\u0003L\u001f\u0000\u02b9\u02b8\u0001\u0000\u0000"+ + "\u0000\u02ba\u02bd\u0001\u0000\u0000\u0000\u02bb\u02b9\u0001\u0000\u0000"+ + "\u0000\u02bb\u02bc\u0001\u0000\u0000\u0000\u02bc\u02bf\u0001\u0000\u0000"+ + "\u0000\u02bd\u02bb\u0001\u0000\u0000\u0000\u02be\u02b7\u0001\u0000\u0000"+ + "\u0000\u02be\u02bf\u0001\u0000\u0000\u0000\u02bf\u02c0\u0001\u0000\u0000"+ + "\u0000\u02c0\u02c1\u0003T#\u0000\u02c1\u02cb\u0001\u0000\u0000\u0000\u02c2"+ + "\u02c4\u0003t3\u0000\u02c3\u02c5\u0003L\u001f\u0000\u02c4\u02c3\u0001"+ + "\u0000\u0000\u0000\u02c5\u02c6\u0001\u0000\u0000\u0000\u02c6\u02c4\u0001"+ + "\u0000\u0000\u0000\u02c6\u02c7\u0001\u0000\u0000\u0000\u02c7\u02c8\u0001"+ + "\u0000\u0000\u0000\u02c8\u02c9\u0003T#\u0000\u02c9\u02cb\u0001\u0000\u0000"+ + "\u0000\u02ca\u02a1\u0001\u0000\u0000\u0000\u02ca\u02ac\u0001\u0000\u0000"+ + "\u0000\u02ca\u02b3\u0001\u0000\u0000\u0000\u02ca\u02c2\u0001\u0000\u0000"+ + "\u0000\u02cbe\u0001\u0000\u0000\u0000\u02cc\u02cd\u0005b\u0000\u0000\u02cd"+ + "\u02ce\u0005y\u0000\u0000\u02ceg\u0001\u0000\u0000\u0000\u02cf\u02d0\u0005"+ + "a\u0000\u0000\u02d0\u02d1\u0005n\u0000\u0000\u02d1\u02d2\u0005d\u0000"+ + "\u0000\u02d2i\u0001\u0000\u0000\u0000\u02d3\u02d4\u0005a\u0000\u0000\u02d4"+ + "\u02d5\u0005s\u0000\u0000\u02d5\u02d6\u0005c\u0000\u0000\u02d6k\u0001"+ + "\u0000\u0000\u0000\u02d7\u02d8\u0005=\u0000\u0000\u02d8m\u0001\u0000\u0000"+ + "\u0000\u02d9\u02da\u0005:\u0000\u0000\u02da\u02db\u0005:\u0000\u0000\u02db"+ + "o\u0001\u0000\u0000\u0000\u02dc\u02dd\u0005,\u0000\u0000\u02ddq\u0001"+ + "\u0000\u0000\u0000\u02de\u02df\u0005d\u0000\u0000\u02df\u02e0\u0005e\u0000"+ + "\u0000\u02e0\u02e1\u0005s\u0000\u0000\u02e1\u02e2\u0005c\u0000\u0000\u02e2"+ + "s\u0001\u0000\u0000\u0000\u02e3\u02e4\u0005.\u0000\u0000\u02e4u\u0001"+ + "\u0000\u0000\u0000\u02e5\u02e6\u0005f\u0000\u0000\u02e6\u02e7\u0005a\u0000"+ + "\u0000\u02e7\u02e8\u0005l\u0000\u0000\u02e8\u02e9\u0005s\u0000\u0000\u02e9"+ + "\u02ea\u0005e\u0000\u0000\u02eaw\u0001\u0000\u0000\u0000\u02eb\u02ec\u0005"+ + "f\u0000\u0000\u02ec\u02ed\u0005i\u0000\u0000\u02ed\u02ee\u0005r\u0000"+ + "\u0000\u02ee\u02ef\u0005s\u0000\u0000\u02ef\u02f0\u0005t\u0000\u0000\u02f0"+ + "y\u0001\u0000\u0000\u0000\u02f1\u02f2\u0005l\u0000\u0000\u02f2\u02f3\u0005"+ + "a\u0000\u0000\u02f3\u02f4\u0005s\u0000\u0000\u02f4\u02f5\u0005t\u0000"+ + "\u0000\u02f5{\u0001\u0000\u0000\u0000\u02f6\u02f7\u0005(\u0000\u0000\u02f7"+ + "}\u0001\u0000\u0000\u0000\u02f8\u02f9\u0005i\u0000\u0000\u02f9\u02fa\u0005"+ + "n\u0000\u0000\u02fa\u007f\u0001\u0000\u0000\u0000\u02fb\u02fc\u0005i\u0000"+ + "\u0000\u02fc\u02fd\u0005s\u0000\u0000\u02fd\u0081\u0001\u0000\u0000\u0000"+ + "\u02fe\u02ff\u0005l\u0000\u0000\u02ff\u0300\u0005i\u0000\u0000\u0300\u0301"+ + "\u0005k\u0000\u0000\u0301\u0302\u0005e\u0000\u0000\u0302\u0083\u0001\u0000"+ + "\u0000\u0000\u0303\u0304\u0005n\u0000\u0000\u0304\u0305\u0005o\u0000\u0000"+ + "\u0305\u0306\u0005t\u0000\u0000\u0306\u0085\u0001\u0000\u0000\u0000\u0307"+ + "\u0308\u0005n\u0000\u0000\u0308\u0309\u0005u\u0000\u0000\u0309\u030a\u0005"+ + "l\u0000\u0000\u030a\u030b\u0005l\u0000\u0000\u030b\u0087\u0001\u0000\u0000"+ + "\u0000\u030c\u030d\u0005n\u0000\u0000\u030d\u030e\u0005u\u0000\u0000\u030e"+ + "\u030f\u0005l\u0000\u0000\u030f\u0310\u0005l\u0000\u0000\u0310\u0311\u0005"+ + "s\u0000\u0000\u0311\u0089\u0001\u0000\u0000\u0000\u0312\u0313\u0005o\u0000"+ + "\u0000\u0313\u0314\u0005r\u0000\u0000\u0314\u008b\u0001\u0000\u0000\u0000"+ + "\u0315\u0316\u0005?\u0000\u0000\u0316\u008d\u0001\u0000\u0000\u0000\u0317"+ + "\u0318\u0005r\u0000\u0000\u0318\u0319\u0005l\u0000\u0000\u0319\u031a\u0005"+ + "i\u0000\u0000\u031a\u031b\u0005k\u0000\u0000\u031b\u031c\u0005e\u0000"+ + "\u0000\u031c\u008f\u0001\u0000\u0000\u0000\u031d\u031e\u0005)\u0000\u0000"+ + "\u031e\u0091\u0001\u0000\u0000\u0000\u031f\u0320\u0005t\u0000\u0000\u0320"+ + "\u0321\u0005r\u0000\u0000\u0321\u0322\u0005u\u0000\u0000\u0322\u0323\u0005"+ + "e\u0000\u0000\u0323\u0093\u0001\u0000\u0000\u0000\u0324\u0325\u0005=\u0000"+ + "\u0000\u0325\u0326\u0005=\u0000\u0000\u0326\u0095\u0001\u0000\u0000\u0000"+ + "\u0327\u0328\u0005=\u0000\u0000\u0328\u0329\u0005~\u0000\u0000\u0329\u0097"+ + "\u0001\u0000\u0000\u0000\u032a\u032b\u0005!\u0000\u0000\u032b\u032c\u0005"+ + "=\u0000\u0000\u032c\u0099\u0001\u0000\u0000\u0000\u032d\u032e\u0005<\u0000"+ + "\u0000\u032e\u009b\u0001\u0000\u0000\u0000\u032f\u0330\u0005<\u0000\u0000"+ + "\u0330\u0331\u0005=\u0000\u0000\u0331\u009d\u0001\u0000\u0000\u0000\u0332"+ + "\u0333\u0005>\u0000\u0000\u0333\u009f\u0001\u0000\u0000\u0000\u0334\u0335"+ + "\u0005>\u0000\u0000\u0335\u0336\u0005=\u0000\u0000\u0336\u00a1\u0001\u0000"+ + "\u0000\u0000\u0337\u0338\u0005+\u0000\u0000\u0338\u00a3\u0001\u0000\u0000"+ + "\u0000\u0339\u033a\u0005-\u0000\u0000\u033a\u00a5\u0001\u0000\u0000\u0000"+ + "\u033b\u033c\u0005*\u0000\u0000\u033c\u00a7\u0001\u0000\u0000\u0000\u033d"+ + "\u033e\u0005/\u0000\u0000\u033e\u00a9\u0001\u0000\u0000\u0000\u033f\u0340"+ + "\u0005%\u0000\u0000\u0340\u00ab\u0001\u0000\u0000\u0000\u0341\u0342\u0005"+ + "[\u0000\u0000\u0342\u0343\u0001\u0000\u0000\u0000\u0343\u0344\u0006O\u0000"+ + "\u0000\u0344\u0345\u0006O\u0000\u0000\u0345\u00ad\u0001\u0000\u0000\u0000"+ + "\u0346\u0347\u0005]\u0000\u0000\u0347\u0348\u0001\u0000\u0000\u0000\u0348"+ + "\u0349\u0006P\u000e\u0000\u0349\u034a\u0006P\u000e\u0000\u034a\u00af\u0001"+ + "\u0000\u0000\u0000\u034b\u034f\u0003N \u0000\u034c\u034e\u0003^(\u0000"+ + "\u034d\u034c\u0001\u0000\u0000\u0000\u034e\u0351\u0001\u0000\u0000\u0000"+ + "\u034f\u034d\u0001\u0000\u0000\u0000\u034f\u0350\u0001\u0000\u0000\u0000"+ + "\u0350\u035c\u0001\u0000\u0000\u0000\u0351\u034f\u0001\u0000\u0000\u0000"+ + "\u0352\u0355\u0003\\\'\u0000\u0353\u0355\u0003V$\u0000\u0354\u0352\u0001"+ + "\u0000\u0000\u0000\u0354\u0353\u0001\u0000\u0000\u0000\u0355\u0357\u0001"+ + "\u0000\u0000\u0000\u0356\u0358\u0003^(\u0000\u0357\u0356\u0001\u0000\u0000"+ + "\u0000\u0358\u0359\u0001\u0000\u0000\u0000\u0359\u0357\u0001\u0000\u0000"+ + "\u0000\u0359\u035a\u0001\u0000\u0000\u0000\u035a\u035c\u0001\u0000\u0000"+ + "\u0000\u035b\u034b\u0001\u0000\u0000\u0000\u035b\u0354\u0001\u0000\u0000"+ + "\u0000\u035c\u00b1\u0001\u0000\u0000\u0000\u035d\u035f\u0003X%\u0000\u035e"+ + "\u0360\u0003Z&\u0000\u035f\u035e\u0001\u0000\u0000\u0000\u0360\u0361\u0001"+ + "\u0000\u0000\u0000\u0361\u035f\u0001\u0000\u0000\u0000\u0361\u0362\u0001"+ + "\u0000\u0000\u0000\u0362\u0363\u0001\u0000\u0000\u0000\u0363\u0364\u0003"+ + "X%\u0000\u0364\u00b3\u0001\u0000\u0000\u0000\u0365\u0366\u0003\u00b2R"+ + "\u0000\u0366\u00b5\u0001\u0000\u0000\u0000\u0367\u0368\u00036\u0014\u0000"+ + "\u0368\u0369\u0001\u0000\u0000\u0000\u0369\u036a\u0006T\n\u0000\u036a"+ + "\u00b7\u0001\u0000\u0000\u0000\u036b\u036c\u00038\u0015\u0000\u036c\u036d"+ + "\u0001\u0000\u0000\u0000\u036d\u036e\u0006U\n\u0000\u036e\u00b9\u0001"+ + "\u0000\u0000\u0000\u036f\u0370\u0003:\u0016\u0000\u0370\u0371\u0001\u0000"+ + "\u0000\u0000\u0371\u0372\u0006V\n\u0000\u0372\u00bb\u0001\u0000\u0000"+ + "\u0000\u0373\u0374\u0003J\u001e\u0000\u0374\u0375\u0001\u0000\u0000\u0000"+ + "\u0375\u0376\u0006W\r\u0000\u0376\u0377\u0006W\u000e\u0000\u0377\u00bd"+ + "\u0001\u0000\u0000\u0000\u0378\u0379\u0003\u00acO\u0000\u0379\u037a\u0001"+ + "\u0000\u0000\u0000\u037a\u037b\u0006X\u000b\u0000\u037b\u00bf\u0001\u0000"+ + "\u0000\u0000\u037c\u037d\u0003\u00aeP\u0000\u037d\u037e\u0001\u0000\u0000"+ + "\u0000\u037e\u037f\u0006Y\u000f\u0000\u037f\u00c1\u0001\u0000\u0000\u0000"+ + "\u0380\u0381\u0003p1\u0000\u0381\u0382\u0001\u0000\u0000\u0000\u0382\u0383"+ + "\u0006Z\u0010\u0000\u0383\u00c3\u0001\u0000\u0000\u0000\u0384\u0385\u0003"+ + "l/\u0000\u0385\u0386\u0001\u0000\u0000\u0000\u0386\u0387\u0006[\u0011"+ + "\u0000\u0387\u00c5\u0001\u0000\u0000\u0000\u0388\u0389\u0003`)\u0000\u0389"+ + "\u038a\u0001\u0000\u0000\u0000\u038a\u038b\u0006\\\u0012\u0000\u038b\u00c7"+ + "\u0001\u0000\u0000\u0000\u038c\u038d\u0005m\u0000\u0000\u038d\u038e\u0005"+ + "e\u0000\u0000\u038e\u038f\u0005t\u0000\u0000\u038f\u0390\u0005a\u0000"+ + "\u0000\u0390\u0391\u0005d\u0000\u0000\u0391\u0392\u0005a\u0000\u0000\u0392"+ + "\u0393\u0005t\u0000\u0000\u0393\u0394\u0005a\u0000\u0000\u0394\u00c9\u0001"+ + "\u0000\u0000\u0000\u0395\u0396\u0003>\u0018\u0000\u0396\u0397\u0001\u0000"+ + "\u0000\u0000\u0397\u0398\u0006^\u0013\u0000\u0398\u00cb\u0001\u0000\u0000"+ + "\u0000\u0399\u039a\u00036\u0014\u0000\u039a\u039b\u0001\u0000\u0000\u0000"+ + "\u039b\u039c\u0006_\n\u0000\u039c\u00cd\u0001\u0000\u0000\u0000\u039d"+ + "\u039e\u00038\u0015\u0000\u039e\u039f\u0001\u0000\u0000\u0000\u039f\u03a0"+ + "\u0006`\n\u0000\u03a0\u00cf\u0001\u0000\u0000\u0000\u03a1\u03a2\u0003"+ + ":\u0016\u0000\u03a2\u03a3\u0001\u0000\u0000\u0000\u03a3\u03a4\u0006a\n"+ + "\u0000\u03a4\u00d1\u0001\u0000\u0000\u0000\u03a5\u03a6\u0003J\u001e\u0000"+ + "\u03a6\u03a7\u0001\u0000\u0000\u0000\u03a7\u03a8\u0006b\r\u0000\u03a8"+ + "\u03a9\u0006b\u000e\u0000\u03a9\u00d3\u0001\u0000\u0000\u0000\u03aa\u03ab"+ + "\u0003t3\u0000\u03ab\u03ac\u0001\u0000\u0000\u0000\u03ac\u03ad\u0006c"+ + "\u0014\u0000\u03ad\u00d5\u0001\u0000\u0000\u0000\u03ae\u03af\u0003p1\u0000"+ + "\u03af\u03b0\u0001\u0000\u0000\u0000\u03b0\u03b1\u0006d\u0010\u0000\u03b1"+ + "\u00d7\u0001\u0000\u0000\u0000\u03b2\u03b7\u0003N \u0000\u03b3\u03b7\u0003"+ + "L\u001f\u0000\u03b4\u03b7\u0003\\\'\u0000\u03b5\u03b7\u0003\u00a6L\u0000"+ + "\u03b6\u03b2\u0001\u0000\u0000\u0000\u03b6\u03b3\u0001\u0000\u0000\u0000"+ + "\u03b6\u03b4\u0001\u0000\u0000\u0000\u03b6\u03b5\u0001\u0000\u0000\u0000"+ + "\u03b7\u00d9\u0001\u0000\u0000\u0000\u03b8\u03bb\u0003N \u0000\u03b9\u03bb"+ + "\u0003\u00a6L\u0000\u03ba\u03b8\u0001\u0000\u0000\u0000\u03ba\u03b9\u0001"+ + "\u0000\u0000\u0000\u03bb\u03bf\u0001\u0000\u0000\u0000\u03bc\u03be\u0003"+ + "\u00d8e\u0000\u03bd\u03bc\u0001\u0000\u0000\u0000\u03be\u03c1\u0001\u0000"+ + "\u0000\u0000\u03bf\u03bd\u0001\u0000\u0000\u0000\u03bf\u03c0\u0001\u0000"+ + "\u0000\u0000\u03c0\u03cc\u0001\u0000\u0000\u0000\u03c1\u03bf\u0001\u0000"+ + "\u0000\u0000\u03c2\u03c5\u0003\\\'\u0000\u03c3\u03c5\u0003V$\u0000\u03c4"+ + "\u03c2\u0001\u0000\u0000\u0000\u03c4\u03c3\u0001\u0000\u0000\u0000\u03c5"+ + "\u03c7\u0001\u0000\u0000\u0000\u03c6\u03c8\u0003\u00d8e\u0000\u03c7\u03c6"+ + "\u0001\u0000\u0000\u0000\u03c8\u03c9\u0001\u0000\u0000\u0000\u03c9\u03c7"+ + "\u0001\u0000\u0000\u0000\u03c9\u03ca\u0001\u0000\u0000\u0000\u03ca\u03cc"+ + "\u0001\u0000\u0000\u0000\u03cb\u03ba\u0001\u0000\u0000\u0000\u03cb\u03c4"+ + "\u0001\u0000\u0000\u0000\u03cc\u00db\u0001\u0000\u0000\u0000\u03cd\u03d0"+ + "\u0003\u00daf\u0000\u03ce\u03d0\u0003\u00b2R\u0000\u03cf\u03cd\u0001\u0000"+ + "\u0000\u0000\u03cf\u03ce\u0001\u0000\u0000\u0000\u03d0\u03d1\u0001\u0000"+ + "\u0000\u0000\u03d1\u03cf\u0001\u0000\u0000\u0000\u03d1\u03d2\u0001\u0000"+ + "\u0000\u0000\u03d2\u00dd\u0001\u0000\u0000\u0000\u03d3\u03d4\u00036\u0014"+ + "\u0000\u03d4\u03d5\u0001\u0000\u0000\u0000\u03d5\u03d6\u0006h\n\u0000"+ + "\u03d6\u00df\u0001\u0000\u0000\u0000\u03d7\u03d8\u00038\u0015\u0000\u03d8"+ + "\u03d9\u0001\u0000\u0000\u0000\u03d9\u03da\u0006i\n\u0000\u03da\u00e1"+ + "\u0001\u0000\u0000\u0000\u03db\u03dc\u0003:\u0016\u0000\u03dc\u03dd\u0001"+ + "\u0000\u0000\u0000\u03dd\u03de\u0006j\n\u0000\u03de\u00e3\u0001\u0000"+ + "\u0000\u0000\u03df\u03e0\u0003J\u001e\u0000\u03e0\u03e1\u0001\u0000\u0000"+ + "\u0000\u03e1\u03e2\u0006k\r\u0000\u03e2\u03e3\u0006k\u000e\u0000\u03e3"+ + "\u00e5\u0001\u0000\u0000\u0000\u03e4\u03e5\u0003l/\u0000\u03e5\u03e6\u0001"+ + "\u0000\u0000\u0000\u03e6\u03e7\u0006l\u0011\u0000\u03e7\u00e7\u0001\u0000"+ + "\u0000\u0000\u03e8\u03e9\u0003p1\u0000\u03e9\u03ea\u0001\u0000\u0000\u0000"+ + "\u03ea\u03eb\u0006m\u0010\u0000\u03eb\u00e9\u0001\u0000\u0000\u0000\u03ec"+ + "\u03ed\u0003t3\u0000\u03ed\u03ee\u0001\u0000\u0000\u0000\u03ee\u03ef\u0006"+ + "n\u0014\u0000\u03ef\u00eb\u0001\u0000\u0000\u0000\u03f0\u03f1\u0005a\u0000"+ + "\u0000\u03f1\u03f2\u0005s\u0000\u0000\u03f2\u00ed\u0001\u0000\u0000\u0000"+ + "\u03f3\u03f4\u0003\u00dcg\u0000\u03f4\u03f5\u0001\u0000\u0000\u0000\u03f5"+ + "\u03f6\u0006p\u0015\u0000\u03f6\u00ef\u0001\u0000\u0000\u0000\u03f7\u03f8"+ + "\u00036\u0014\u0000\u03f8\u03f9\u0001\u0000\u0000\u0000\u03f9\u03fa\u0006"+ + "q\n\u0000\u03fa\u00f1\u0001\u0000\u0000\u0000\u03fb\u03fc\u00038\u0015"+ + "\u0000\u03fc\u03fd\u0001\u0000\u0000\u0000\u03fd\u03fe\u0006r\n\u0000"+ + "\u03fe\u00f3\u0001\u0000\u0000\u0000\u03ff\u0400\u0003:\u0016\u0000\u0400"+ + "\u0401\u0001\u0000\u0000\u0000\u0401\u0402\u0006s\n\u0000\u0402\u00f5"+ + "\u0001\u0000\u0000\u0000\u0403\u0404\u0003J\u001e\u0000\u0404\u0405\u0001"+ + "\u0000\u0000\u0000\u0405\u0406\u0006t\r\u0000\u0406\u0407\u0006t\u000e"+ + "\u0000\u0407\u00f7\u0001\u0000\u0000\u0000\u0408\u0409\u0003\u00acO\u0000"+ + "\u0409\u040a\u0001\u0000\u0000\u0000\u040a\u040b\u0006u\u000b\u0000\u040b"+ + "\u040c\u0006u\u0016\u0000\u040c\u00f9\u0001\u0000\u0000\u0000\u040d\u040e"+ + "\u0005o\u0000\u0000\u040e\u040f\u0005n\u0000\u0000\u040f\u0410\u0001\u0000"+ + "\u0000\u0000\u0410\u0411\u0006v\u0017\u0000\u0411\u00fb\u0001\u0000\u0000"+ + "\u0000\u0412\u0413\u0005w\u0000\u0000\u0413\u0414\u0005i\u0000\u0000\u0414"+ + "\u0415\u0005t\u0000\u0000\u0415\u0416\u0005h\u0000\u0000\u0416\u0417\u0001"+ + "\u0000\u0000\u0000\u0417\u0418\u0006w\u0017\u0000\u0418\u00fd\u0001\u0000"+ + "\u0000\u0000\u0419\u041a\b\f\u0000\u0000\u041a\u00ff\u0001\u0000\u0000"+ + "\u0000\u041b\u041d\u0003\u00fex\u0000\u041c\u041b\u0001\u0000\u0000\u0000"+ + "\u041d\u041e\u0001\u0000\u0000\u0000\u041e\u041c\u0001\u0000\u0000\u0000"+ + "\u041e\u041f\u0001\u0000\u0000\u0000\u041f\u0420\u0001\u0000\u0000\u0000"+ + "\u0420\u0421\u0003\u0144\u009b\u0000\u0421\u0423\u0001\u0000\u0000\u0000"+ + "\u0422\u041c\u0001\u0000\u0000\u0000\u0422\u0423\u0001\u0000\u0000\u0000"+ + "\u0423\u0425\u0001\u0000\u0000\u0000\u0424\u0426\u0003\u00fex\u0000\u0425"+ + "\u0424\u0001\u0000\u0000\u0000\u0426\u0427\u0001\u0000\u0000\u0000\u0427"+ + "\u0425\u0001\u0000\u0000\u0000\u0427\u0428\u0001\u0000\u0000\u0000\u0428"+ + "\u0101\u0001\u0000\u0000\u0000\u0429\u042a\u0003\u00b4S\u0000\u042a\u042b"+ + "\u0001\u0000\u0000\u0000\u042b\u042c\u0006z\u0018\u0000\u042c\u0103\u0001"+ + "\u0000\u0000\u0000\u042d\u042e\u0003\u0100y\u0000\u042e\u042f\u0001\u0000"+ + "\u0000\u0000\u042f\u0430\u0006{\u0019\u0000\u0430\u0105\u0001\u0000\u0000"+ + "\u0000\u0431\u0432\u00036\u0014\u0000\u0432\u0433\u0001\u0000\u0000\u0000"+ + "\u0433\u0434\u0006|\n\u0000\u0434\u0107\u0001\u0000\u0000\u0000\u0435"+ + "\u0436\u00038\u0015\u0000\u0436\u0437\u0001\u0000\u0000\u0000\u0437\u0438"+ + "\u0006}\n\u0000\u0438\u0109\u0001\u0000\u0000\u0000\u0439\u043a\u0003"+ + ":\u0016\u0000\u043a\u043b\u0001\u0000\u0000\u0000\u043b\u043c\u0006~\n"+ + "\u0000\u043c\u010b\u0001\u0000\u0000\u0000\u043d\u043e\u0003J\u001e\u0000"+ + "\u043e\u043f\u0001\u0000\u0000\u0000\u043f\u0440\u0006\u007f\r\u0000\u0440"+ + "\u0441\u0006\u007f\u000e\u0000\u0441\u0442\u0006\u007f\u000e\u0000\u0442"+ + "\u010d\u0001\u0000\u0000\u0000\u0443\u0444\u0003l/\u0000\u0444\u0445\u0001"+ + "\u0000\u0000\u0000\u0445\u0446\u0006\u0080\u0011\u0000\u0446\u010f\u0001"+ + "\u0000\u0000\u0000\u0447\u0448\u0003p1\u0000\u0448\u0449\u0001\u0000\u0000"+ + "\u0000\u0449\u044a\u0006\u0081\u0010\u0000\u044a\u0111\u0001\u0000\u0000"+ + "\u0000\u044b\u044c\u0003t3\u0000\u044c\u044d\u0001\u0000\u0000\u0000\u044d"+ + "\u044e\u0006\u0082\u0014\u0000\u044e\u0113\u0001\u0000\u0000\u0000\u044f"+ + "\u0450\u0003\u00fcw\u0000\u0450\u0451\u0001\u0000\u0000\u0000\u0451\u0452"+ + "\u0006\u0083\u001a\u0000\u0452\u0115\u0001\u0000\u0000\u0000\u0453\u0454"+ + "\u0003\u00dcg\u0000\u0454\u0455\u0001\u0000\u0000\u0000\u0455\u0456\u0006"+ + "\u0084\u0015\u0000\u0456\u0117\u0001\u0000\u0000\u0000\u0457\u0458\u0003"+ + "\u00b4S\u0000\u0458\u0459\u0001\u0000\u0000\u0000\u0459\u045a\u0006\u0085"+ + "\u0018\u0000\u045a\u0119\u0001\u0000\u0000\u0000\u045b\u045c\u00036\u0014"+ + "\u0000\u045c\u045d\u0001\u0000\u0000\u0000\u045d\u045e\u0006\u0086\n\u0000"+ + "\u045e\u011b\u0001\u0000\u0000\u0000\u045f\u0460\u00038\u0015\u0000\u0460"+ + "\u0461\u0001\u0000\u0000\u0000\u0461\u0462\u0006\u0087\n\u0000\u0462\u011d"+ + "\u0001\u0000\u0000\u0000\u0463\u0464\u0003:\u0016\u0000\u0464\u0465\u0001"+ + "\u0000\u0000\u0000\u0465\u0466\u0006\u0088\n\u0000\u0466\u011f\u0001\u0000"+ + "\u0000\u0000\u0467\u0468\u0003J\u001e\u0000\u0468\u0469\u0001\u0000\u0000"+ + "\u0000\u0469\u046a\u0006\u0089\r\u0000\u046a\u046b\u0006\u0089\u000e\u0000"+ + "\u046b\u0121\u0001\u0000\u0000\u0000\u046c\u046d\u0003t3\u0000\u046d\u046e"+ + "\u0001\u0000\u0000\u0000\u046e\u046f\u0006\u008a\u0014\u0000\u046f\u0123"+ + "\u0001\u0000\u0000\u0000\u0470\u0471\u0003\u00b4S\u0000\u0471\u0472\u0001"+ + "\u0000\u0000\u0000\u0472\u0473\u0006\u008b\u0018\u0000\u0473\u0125\u0001"+ + "\u0000\u0000\u0000\u0474\u0475\u0003\u00b0Q\u0000\u0475\u0476\u0001\u0000"+ + "\u0000\u0000\u0476\u0477\u0006\u008c\u001b\u0000\u0477\u0127\u0001\u0000"+ + "\u0000\u0000\u0478\u0479\u00036\u0014\u0000\u0479\u047a\u0001\u0000\u0000"+ + "\u0000\u047a\u047b\u0006\u008d\n\u0000\u047b\u0129\u0001\u0000\u0000\u0000"+ + "\u047c\u047d\u00038\u0015\u0000\u047d\u047e\u0001\u0000\u0000\u0000\u047e"+ + "\u047f\u0006\u008e\n\u0000\u047f\u012b\u0001\u0000\u0000\u0000\u0480\u0481"+ + "\u0003:\u0016\u0000\u0481\u0482\u0001\u0000\u0000\u0000\u0482\u0483\u0006"+ + "\u008f\n\u0000\u0483\u012d\u0001\u0000\u0000\u0000\u0484\u0485\u0003J"+ + "\u001e\u0000\u0485\u0486\u0001\u0000\u0000\u0000\u0486\u0487\u0006\u0090"+ + "\r\u0000\u0487\u0488\u0006\u0090\u000e\u0000\u0488\u012f\u0001\u0000\u0000"+ + "\u0000\u0489\u048a\u0005i\u0000\u0000\u048a\u048b\u0005n\u0000\u0000\u048b"+ + "\u048c\u0005f\u0000\u0000\u048c\u048d\u0005o\u0000\u0000\u048d\u0131\u0001"+ + "\u0000\u0000\u0000\u048e\u048f\u00036\u0014\u0000\u048f\u0490\u0001\u0000"+ + "\u0000\u0000\u0490\u0491\u0006\u0092\n\u0000\u0491\u0133\u0001\u0000\u0000"+ + "\u0000\u0492\u0493\u00038\u0015\u0000\u0493\u0494\u0001\u0000\u0000\u0000"+ + "\u0494\u0495\u0006\u0093\n\u0000\u0495\u0135\u0001\u0000\u0000\u0000\u0496"+ + "\u0497\u0003:\u0016\u0000\u0497\u0498\u0001\u0000\u0000\u0000\u0498\u0499"+ + "\u0006\u0094\n\u0000\u0499\u0137\u0001\u0000\u0000\u0000\u049a\u049b\u0003"+ + "J\u001e\u0000\u049b\u049c\u0001\u0000\u0000\u0000\u049c\u049d\u0006\u0095"+ + "\r\u0000\u049d\u049e\u0006\u0095\u000e\u0000\u049e\u0139\u0001\u0000\u0000"+ + "\u0000\u049f\u04a0\u0005f\u0000\u0000\u04a0\u04a1\u0005u\u0000\u0000\u04a1"+ + "\u04a2\u0005n\u0000\u0000\u04a2\u04a3\u0005c\u0000\u0000\u04a3\u04a4\u0005"+ + "t\u0000\u0000\u04a4\u04a5\u0005i\u0000\u0000\u04a5\u04a6\u0005o\u0000"+ + "\u0000\u04a6\u04a7\u0005n\u0000\u0000\u04a7\u04a8\u0005s\u0000\u0000\u04a8"+ + "\u013b\u0001\u0000\u0000\u0000\u04a9\u04aa\u00036\u0014\u0000\u04aa\u04ab"+ + "\u0001\u0000\u0000\u0000\u04ab\u04ac\u0006\u0097\n\u0000\u04ac\u013d\u0001"+ + "\u0000\u0000\u0000\u04ad\u04ae\u00038\u0015\u0000\u04ae\u04af\u0001\u0000"+ + "\u0000\u0000\u04af\u04b0\u0006\u0098\n\u0000\u04b0\u013f\u0001\u0000\u0000"+ + "\u0000\u04b1\u04b2\u0003:\u0016\u0000\u04b2\u04b3\u0001\u0000\u0000\u0000"+ + "\u04b3\u04b4\u0006\u0099\n\u0000\u04b4\u0141\u0001\u0000\u0000\u0000\u04b5"+ + "\u04b6\u0003\u00aeP\u0000\u04b6\u04b7\u0001\u0000\u0000\u0000\u04b7\u04b8"+ + "\u0006\u009a\u000f\u0000\u04b8\u04b9\u0006\u009a\u000e\u0000\u04b9\u0143"+ + "\u0001\u0000\u0000\u0000\u04ba\u04bb\u0005:\u0000\u0000\u04bb\u0145\u0001"+ + "\u0000\u0000\u0000\u04bc\u04c2\u0003V$\u0000\u04bd\u04c2\u0003L\u001f"+ + "\u0000\u04be\u04c2\u0003t3\u0000\u04bf\u04c2\u0003N \u0000\u04c0\u04c2"+ + "\u0003\\\'\u0000\u04c1\u04bc\u0001\u0000\u0000\u0000\u04c1\u04bd\u0001"+ + "\u0000\u0000\u0000\u04c1\u04be\u0001\u0000\u0000\u0000\u04c1\u04bf\u0001"+ + "\u0000\u0000\u0000\u04c1\u04c0\u0001\u0000\u0000\u0000\u04c2\u04c3\u0001"+ + "\u0000\u0000\u0000\u04c3\u04c1\u0001\u0000\u0000\u0000\u04c3\u04c4\u0001"+ + "\u0000\u0000\u0000\u04c4\u0147\u0001\u0000\u0000\u0000\u04c5\u04c6\u0003"+ + "6\u0014\u0000\u04c6\u04c7\u0001\u0000\u0000\u0000\u04c7\u04c8\u0006\u009d"+ + "\n\u0000\u04c8\u0149\u0001\u0000\u0000\u0000\u04c9\u04ca\u00038\u0015"+ + "\u0000\u04ca\u04cb\u0001\u0000\u0000\u0000\u04cb\u04cc\u0006\u009e\n\u0000"+ + "\u04cc\u014b\u0001\u0000\u0000\u0000\u04cd\u04ce\u0003:\u0016\u0000\u04ce"+ + "\u04cf\u0001\u0000\u0000\u0000\u04cf\u04d0\u0006\u009f\n\u0000\u04d0\u014d"+ + "\u0001\u0000\u0000\u0000\u04d1\u04d2\u0003J\u001e\u0000\u04d2\u04d3\u0001"+ + "\u0000\u0000\u0000\u04d3\u04d4\u0006\u00a0\r\u0000\u04d4\u04d5\u0006\u00a0"+ + "\u000e\u0000\u04d5\u014f\u0001\u0000\u0000\u0000\u04d6\u04d7\u0003>\u0018"+ + "\u0000\u04d7\u04d8\u0001\u0000\u0000\u0000\u04d8\u04d9\u0006\u00a1\u0013"+ + "\u0000\u04d9\u04da\u0006\u00a1\u000e\u0000\u04da\u04db\u0006\u00a1\u001c"+ + "\u0000\u04db\u0151\u0001\u0000\u0000\u0000\u04dc\u04dd\u00036\u0014\u0000"+ + "\u04dd\u04de\u0001\u0000\u0000\u0000\u04de\u04df\u0006\u00a2\n\u0000\u04df"+ + "\u0153\u0001\u0000\u0000\u0000\u04e0\u04e1\u00038\u0015\u0000\u04e1\u04e2"+ + "\u0001\u0000\u0000\u0000\u04e2\u04e3\u0006\u00a3\n\u0000\u04e3\u0155\u0001"+ + "\u0000\u0000\u0000\u04e4\u04e5\u0003:\u0016\u0000\u04e5\u04e6\u0001\u0000"+ + "\u0000\u0000\u04e6\u04e7\u0006\u00a4\n\u0000\u04e7\u0157\u0001\u0000\u0000"+ + "\u0000\u04e8\u04e9\u0003p1\u0000\u04e9\u04ea\u0001\u0000\u0000\u0000\u04ea"+ + "\u04eb\u0006\u00a5\u0010\u0000\u04eb\u04ec\u0006\u00a5\u000e\u0000\u04ec"+ + "\u04ed\u0006\u00a5\u0006\u0000\u04ed\u0159\u0001\u0000\u0000\u0000\u04ee"+ + "\u04ef\u00036\u0014\u0000\u04ef\u04f0\u0001\u0000\u0000\u0000\u04f0\u04f1"+ + "\u0006\u00a6\n\u0000\u04f1\u015b\u0001\u0000\u0000\u0000\u04f2\u04f3\u0003"+ + "8\u0015\u0000\u04f3\u04f4\u0001\u0000\u0000\u0000\u04f4\u04f5\u0006\u00a7"+ + "\n\u0000\u04f5\u015d\u0001\u0000\u0000\u0000\u04f6\u04f7\u0003:\u0016"+ + "\u0000\u04f7\u04f8\u0001\u0000\u0000\u0000\u04f8\u04f9\u0006\u00a8\n\u0000"+ + "\u04f9\u015f\u0001\u0000\u0000\u0000\u04fa\u04fb\u0003\u00b4S\u0000\u04fb"+ + "\u04fc\u0001\u0000\u0000\u0000\u04fc\u04fd\u0006\u00a9\u000e\u0000\u04fd"+ + "\u04fe\u0006\u00a9\u0000\u0000\u04fe\u04ff\u0006\u00a9\u0018\u0000\u04ff"+ + "\u0161\u0001\u0000\u0000\u0000\u0500\u0501\u0003\u00b0Q\u0000\u0501\u0502"+ + "\u0001\u0000\u0000\u0000\u0502\u0503\u0006\u00aa\u000e\u0000\u0503\u0504"+ + "\u0006\u00aa\u0000\u0000\u0504\u0505\u0006\u00aa\u001b\u0000\u0505\u0163"+ + "\u0001\u0000\u0000\u0000\u0506\u0507\u0003f,\u0000\u0507\u0508\u0001\u0000"+ + "\u0000\u0000\u0508\u0509\u0006\u00ab\u000e\u0000\u0509\u050a\u0006\u00ab"+ + "\u0000\u0000\u050a\u050b\u0006\u00ab\u001d\u0000\u050b\u0165\u0001\u0000"+ + "\u0000\u0000\u050c\u050d\u0003J\u001e\u0000\u050d\u050e\u0001\u0000\u0000"+ + "\u0000\u050e\u050f\u0006\u00ac\r\u0000\u050f\u0510\u0006\u00ac\u000e\u0000"+ + "\u0510\u0167\u0001\u0000\u0000\u0000<\u0000\u0001\u0002\u0003\u0004\u0005"+ + "\u0006\u0007\b\t\n\u000b\f\r\u020b\u0215\u0219\u021c\u0225\u0227\u0232"+ + "\u0239\u023e\u0265\u026a\u0273\u027a\u027f\u0281\u028c\u0294\u0297\u0299"+ + "\u029e\u02a3\u02a9\u02b0\u02b5\u02bb\u02be\u02c6\u02ca\u034f\u0354\u0359"+ + "\u035b\u0361\u03b6\u03ba\u03bf\u03c4\u03c9\u03cb\u03cf\u03d1\u041e\u0422"+ + "\u0427\u04c1\u04c3\u001e\u0005\u0002\u0000\u0005\u0004\u0000\u0005\u0006"+ + "\u0000\u0005\u0001\u0000\u0005\u0003\u0000\u0005\n\u0000\u0005\f\u0000"+ + "\u0005\b\u0000\u0005\u0005\u0000\u0005\t\u0000\u0000\u0001\u0000\u0007"+ + "C\u0000\u0005\u0000\u0000\u0007\u001c\u0000\u0004\u0000\u0000\u0007D\u0000"+ + "\u0007%\u0000\u0007#\u0000\u0007\u001d\u0000\u0007\u0018\u0000\u0007\'"+ + "\u0000\u0007N\u0000\u0005\u000b\u0000\u0005\u0007\u0000\u0007F\u0000\u0007"+ + "X\u0000\u0007W\u0000\u0007E\u0000\u0005\r\u0000\u0007 \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 461605d5f0231..d6f90975aefac 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 @@ -73,7 +73,6 @@ null null null null -'options' 'metadata' null null @@ -193,7 +192,6 @@ QUOTED_IDENTIFIER EXPR_LINE_COMMENT EXPR_MULTILINE_COMMENT EXPR_WS -OPTIONS METADATA FROM_LINE_COMMENT FROM_MULTILINE_COMMENT @@ -256,8 +254,6 @@ fields field fromCommand indexIdentifier -fromOptions -configOption metadata metadataOption deprecated_metadata @@ -297,4 +293,4 @@ enrichWithClause atn: -[4, 1, 117, 562, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 120, 8, 1, 10, 1, 12, 1, 123, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 131, 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, 3, 3, 146, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 158, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 165, 8, 5, 10, 5, 12, 5, 168, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 175, 8, 5, 1, 5, 1, 5, 3, 5, 179, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 187, 8, 5, 10, 5, 12, 5, 190, 9, 5, 1, 6, 1, 6, 3, 6, 194, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 201, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 206, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 213, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 219, 8, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 227, 8, 8, 10, 8, 12, 8, 230, 9, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 240, 8, 9, 1, 9, 1, 9, 1, 9, 5, 9, 245, 8, 9, 10, 9, 12, 9, 248, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 256, 8, 10, 10, 10, 12, 10, 259, 9, 10, 3, 10, 261, 8, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 273, 8, 13, 10, 13, 12, 13, 276, 9, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 283, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 289, 8, 15, 10, 15, 12, 15, 292, 9, 15, 1, 15, 3, 15, 295, 8, 15, 1, 15, 3, 15, 298, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 306, 8, 17, 10, 17, 12, 17, 309, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 3, 19, 317, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 323, 8, 20, 10, 20, 12, 20, 326, 9, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 336, 8, 22, 10, 22, 12, 22, 339, 9, 22, 1, 22, 3, 22, 342, 8, 22, 1, 22, 1, 22, 3, 22, 346, 8, 22, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 3, 24, 353, 8, 24, 1, 24, 1, 24, 3, 24, 357, 8, 24, 1, 25, 1, 25, 1, 25, 1, 25, 3, 25, 363, 8, 25, 1, 26, 1, 26, 1, 26, 5, 26, 368, 8, 26, 10, 26, 12, 26, 371, 9, 26, 1, 27, 1, 27, 1, 27, 5, 27, 376, 8, 27, 10, 27, 12, 27, 379, 9, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 398, 8, 30, 10, 30, 12, 30, 401, 9, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 409, 8, 30, 10, 30, 12, 30, 412, 9, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 420, 8, 30, 10, 30, 12, 30, 423, 9, 30, 1, 30, 1, 30, 3, 30, 427, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 436, 8, 32, 10, 32, 12, 32, 439, 9, 32, 1, 33, 1, 33, 3, 33, 443, 8, 33, 1, 33, 1, 33, 3, 33, 447, 8, 33, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 453, 8, 34, 10, 34, 12, 34, 456, 9, 34, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 462, 8, 35, 10, 35, 12, 35, 465, 9, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 471, 8, 36, 10, 36, 12, 36, 474, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 484, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 496, 8, 41, 10, 41, 12, 41, 499, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 3, 44, 509, 8, 44, 1, 45, 3, 45, 512, 8, 45, 1, 45, 1, 45, 1, 46, 3, 46, 517, 8, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 542, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 5, 53, 548, 8, 53, 10, 53, 12, 53, 551, 9, 53, 3, 53, 553, 8, 53, 1, 54, 1, 54, 1, 54, 3, 54, 558, 8, 54, 1, 54, 1, 54, 1, 54, 0, 4, 2, 10, 16, 18, 55, 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, 0, 7, 1, 0, 62, 63, 1, 0, 64, 66, 1, 0, 69, 70, 2, 0, 34, 34, 38, 38, 1, 0, 41, 42, 2, 0, 40, 40, 54, 54, 2, 0, 55, 55, 57, 61, 590, 0, 110, 1, 0, 0, 0, 2, 113, 1, 0, 0, 0, 4, 130, 1, 0, 0, 0, 6, 145, 1, 0, 0, 0, 8, 147, 1, 0, 0, 0, 10, 178, 1, 0, 0, 0, 12, 205, 1, 0, 0, 0, 14, 212, 1, 0, 0, 0, 16, 218, 1, 0, 0, 0, 18, 239, 1, 0, 0, 0, 20, 249, 1, 0, 0, 0, 22, 264, 1, 0, 0, 0, 24, 266, 1, 0, 0, 0, 26, 269, 1, 0, 0, 0, 28, 282, 1, 0, 0, 0, 30, 284, 1, 0, 0, 0, 32, 299, 1, 0, 0, 0, 34, 301, 1, 0, 0, 0, 36, 310, 1, 0, 0, 0, 38, 316, 1, 0, 0, 0, 40, 318, 1, 0, 0, 0, 42, 327, 1, 0, 0, 0, 44, 331, 1, 0, 0, 0, 46, 347, 1, 0, 0, 0, 48, 350, 1, 0, 0, 0, 50, 358, 1, 0, 0, 0, 52, 364, 1, 0, 0, 0, 54, 372, 1, 0, 0, 0, 56, 380, 1, 0, 0, 0, 58, 382, 1, 0, 0, 0, 60, 426, 1, 0, 0, 0, 62, 428, 1, 0, 0, 0, 64, 431, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 448, 1, 0, 0, 0, 70, 457, 1, 0, 0, 0, 72, 466, 1, 0, 0, 0, 74, 475, 1, 0, 0, 0, 76, 479, 1, 0, 0, 0, 78, 485, 1, 0, 0, 0, 80, 489, 1, 0, 0, 0, 82, 492, 1, 0, 0, 0, 84, 500, 1, 0, 0, 0, 86, 504, 1, 0, 0, 0, 88, 508, 1, 0, 0, 0, 90, 511, 1, 0, 0, 0, 92, 516, 1, 0, 0, 0, 94, 520, 1, 0, 0, 0, 96, 522, 1, 0, 0, 0, 98, 524, 1, 0, 0, 0, 100, 527, 1, 0, 0, 0, 102, 531, 1, 0, 0, 0, 104, 534, 1, 0, 0, 0, 106, 537, 1, 0, 0, 0, 108, 557, 1, 0, 0, 0, 110, 111, 3, 2, 1, 0, 111, 112, 5, 0, 0, 1, 112, 1, 1, 0, 0, 0, 113, 114, 6, 1, -1, 0, 114, 115, 3, 4, 2, 0, 115, 121, 1, 0, 0, 0, 116, 117, 10, 1, 0, 0, 117, 118, 5, 28, 0, 0, 118, 120, 3, 6, 3, 0, 119, 116, 1, 0, 0, 0, 120, 123, 1, 0, 0, 0, 121, 119, 1, 0, 0, 0, 121, 122, 1, 0, 0, 0, 122, 3, 1, 0, 0, 0, 123, 121, 1, 0, 0, 0, 124, 131, 3, 98, 49, 0, 125, 131, 3, 30, 15, 0, 126, 131, 3, 24, 12, 0, 127, 131, 3, 44, 22, 0, 128, 131, 3, 102, 51, 0, 129, 131, 3, 104, 52, 0, 130, 124, 1, 0, 0, 0, 130, 125, 1, 0, 0, 0, 130, 126, 1, 0, 0, 0, 130, 127, 1, 0, 0, 0, 130, 128, 1, 0, 0, 0, 130, 129, 1, 0, 0, 0, 131, 5, 1, 0, 0, 0, 132, 146, 3, 46, 23, 0, 133, 146, 3, 50, 25, 0, 134, 146, 3, 62, 31, 0, 135, 146, 3, 68, 34, 0, 136, 146, 3, 64, 32, 0, 137, 146, 3, 48, 24, 0, 138, 146, 3, 8, 4, 0, 139, 146, 3, 70, 35, 0, 140, 146, 3, 72, 36, 0, 141, 146, 3, 76, 38, 0, 142, 146, 3, 78, 39, 0, 143, 146, 3, 106, 53, 0, 144, 146, 3, 80, 40, 0, 145, 132, 1, 0, 0, 0, 145, 133, 1, 0, 0, 0, 145, 134, 1, 0, 0, 0, 145, 135, 1, 0, 0, 0, 145, 136, 1, 0, 0, 0, 145, 137, 1, 0, 0, 0, 145, 138, 1, 0, 0, 0, 145, 139, 1, 0, 0, 0, 145, 140, 1, 0, 0, 0, 145, 141, 1, 0, 0, 0, 145, 142, 1, 0, 0, 0, 145, 143, 1, 0, 0, 0, 145, 144, 1, 0, 0, 0, 146, 7, 1, 0, 0, 0, 147, 148, 5, 19, 0, 0, 148, 149, 3, 10, 5, 0, 149, 9, 1, 0, 0, 0, 150, 151, 6, 5, -1, 0, 151, 152, 5, 47, 0, 0, 152, 179, 3, 10, 5, 7, 153, 179, 3, 14, 7, 0, 154, 179, 3, 12, 6, 0, 155, 157, 3, 14, 7, 0, 156, 158, 5, 47, 0, 0, 157, 156, 1, 0, 0, 0, 157, 158, 1, 0, 0, 0, 158, 159, 1, 0, 0, 0, 159, 160, 5, 44, 0, 0, 160, 161, 5, 43, 0, 0, 161, 166, 3, 14, 7, 0, 162, 163, 5, 37, 0, 0, 163, 165, 3, 14, 7, 0, 164, 162, 1, 0, 0, 0, 165, 168, 1, 0, 0, 0, 166, 164, 1, 0, 0, 0, 166, 167, 1, 0, 0, 0, 167, 169, 1, 0, 0, 0, 168, 166, 1, 0, 0, 0, 169, 170, 5, 53, 0, 0, 170, 179, 1, 0, 0, 0, 171, 172, 3, 14, 7, 0, 172, 174, 5, 45, 0, 0, 173, 175, 5, 47, 0, 0, 174, 173, 1, 0, 0, 0, 174, 175, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 48, 0, 0, 177, 179, 1, 0, 0, 0, 178, 150, 1, 0, 0, 0, 178, 153, 1, 0, 0, 0, 178, 154, 1, 0, 0, 0, 178, 155, 1, 0, 0, 0, 178, 171, 1, 0, 0, 0, 179, 188, 1, 0, 0, 0, 180, 181, 10, 4, 0, 0, 181, 182, 5, 33, 0, 0, 182, 187, 3, 10, 5, 5, 183, 184, 10, 3, 0, 0, 184, 185, 5, 50, 0, 0, 185, 187, 3, 10, 5, 4, 186, 180, 1, 0, 0, 0, 186, 183, 1, 0, 0, 0, 187, 190, 1, 0, 0, 0, 188, 186, 1, 0, 0, 0, 188, 189, 1, 0, 0, 0, 189, 11, 1, 0, 0, 0, 190, 188, 1, 0, 0, 0, 191, 193, 3, 14, 7, 0, 192, 194, 5, 47, 0, 0, 193, 192, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 196, 5, 46, 0, 0, 196, 197, 3, 94, 47, 0, 197, 206, 1, 0, 0, 0, 198, 200, 3, 14, 7, 0, 199, 201, 5, 47, 0, 0, 200, 199, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 203, 5, 52, 0, 0, 203, 204, 3, 94, 47, 0, 204, 206, 1, 0, 0, 0, 205, 191, 1, 0, 0, 0, 205, 198, 1, 0, 0, 0, 206, 13, 1, 0, 0, 0, 207, 213, 3, 16, 8, 0, 208, 209, 3, 16, 8, 0, 209, 210, 3, 96, 48, 0, 210, 211, 3, 16, 8, 0, 211, 213, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 213, 15, 1, 0, 0, 0, 214, 215, 6, 8, -1, 0, 215, 219, 3, 18, 9, 0, 216, 217, 7, 0, 0, 0, 217, 219, 3, 16, 8, 3, 218, 214, 1, 0, 0, 0, 218, 216, 1, 0, 0, 0, 219, 228, 1, 0, 0, 0, 220, 221, 10, 2, 0, 0, 221, 222, 7, 1, 0, 0, 222, 227, 3, 16, 8, 3, 223, 224, 10, 1, 0, 0, 224, 225, 7, 0, 0, 0, 225, 227, 3, 16, 8, 2, 226, 220, 1, 0, 0, 0, 226, 223, 1, 0, 0, 0, 227, 230, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 17, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 231, 232, 6, 9, -1, 0, 232, 240, 3, 60, 30, 0, 233, 240, 3, 52, 26, 0, 234, 240, 3, 20, 10, 0, 235, 236, 5, 43, 0, 0, 236, 237, 3, 10, 5, 0, 237, 238, 5, 53, 0, 0, 238, 240, 1, 0, 0, 0, 239, 231, 1, 0, 0, 0, 239, 233, 1, 0, 0, 0, 239, 234, 1, 0, 0, 0, 239, 235, 1, 0, 0, 0, 240, 246, 1, 0, 0, 0, 241, 242, 10, 1, 0, 0, 242, 243, 5, 36, 0, 0, 243, 245, 3, 22, 11, 0, 244, 241, 1, 0, 0, 0, 245, 248, 1, 0, 0, 0, 246, 244, 1, 0, 0, 0, 246, 247, 1, 0, 0, 0, 247, 19, 1, 0, 0, 0, 248, 246, 1, 0, 0, 0, 249, 250, 3, 56, 28, 0, 250, 260, 5, 43, 0, 0, 251, 261, 5, 64, 0, 0, 252, 257, 3, 10, 5, 0, 253, 254, 5, 37, 0, 0, 254, 256, 3, 10, 5, 0, 255, 253, 1, 0, 0, 0, 256, 259, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 261, 1, 0, 0, 0, 259, 257, 1, 0, 0, 0, 260, 251, 1, 0, 0, 0, 260, 252, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 263, 5, 53, 0, 0, 263, 21, 1, 0, 0, 0, 264, 265, 3, 56, 28, 0, 265, 23, 1, 0, 0, 0, 266, 267, 5, 15, 0, 0, 267, 268, 3, 26, 13, 0, 268, 25, 1, 0, 0, 0, 269, 274, 3, 28, 14, 0, 270, 271, 5, 37, 0, 0, 271, 273, 3, 28, 14, 0, 272, 270, 1, 0, 0, 0, 273, 276, 1, 0, 0, 0, 274, 272, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 27, 1, 0, 0, 0, 276, 274, 1, 0, 0, 0, 277, 283, 3, 10, 5, 0, 278, 279, 3, 52, 26, 0, 279, 280, 5, 35, 0, 0, 280, 281, 3, 10, 5, 0, 281, 283, 1, 0, 0, 0, 282, 277, 1, 0, 0, 0, 282, 278, 1, 0, 0, 0, 283, 29, 1, 0, 0, 0, 284, 285, 5, 6, 0, 0, 285, 290, 3, 32, 16, 0, 286, 287, 5, 37, 0, 0, 287, 289, 3, 32, 16, 0, 288, 286, 1, 0, 0, 0, 289, 292, 1, 0, 0, 0, 290, 288, 1, 0, 0, 0, 290, 291, 1, 0, 0, 0, 291, 294, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 293, 295, 3, 38, 19, 0, 294, 293, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 1, 0, 0, 0, 296, 298, 3, 34, 17, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 31, 1, 0, 0, 0, 299, 300, 5, 24, 0, 0, 300, 33, 1, 0, 0, 0, 301, 302, 5, 74, 0, 0, 302, 307, 3, 36, 18, 0, 303, 304, 5, 37, 0, 0, 304, 306, 3, 36, 18, 0, 305, 303, 1, 0, 0, 0, 306, 309, 1, 0, 0, 0, 307, 305, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 35, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 310, 311, 3, 94, 47, 0, 311, 312, 5, 35, 0, 0, 312, 313, 3, 94, 47, 0, 313, 37, 1, 0, 0, 0, 314, 317, 3, 40, 20, 0, 315, 317, 3, 42, 21, 0, 316, 314, 1, 0, 0, 0, 316, 315, 1, 0, 0, 0, 317, 39, 1, 0, 0, 0, 318, 319, 5, 75, 0, 0, 319, 324, 3, 32, 16, 0, 320, 321, 5, 37, 0, 0, 321, 323, 3, 32, 16, 0, 322, 320, 1, 0, 0, 0, 323, 326, 1, 0, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 41, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 327, 328, 5, 67, 0, 0, 328, 329, 3, 40, 20, 0, 329, 330, 5, 68, 0, 0, 330, 43, 1, 0, 0, 0, 331, 332, 5, 12, 0, 0, 332, 337, 3, 32, 16, 0, 333, 334, 5, 37, 0, 0, 334, 336, 3, 32, 16, 0, 335, 333, 1, 0, 0, 0, 336, 339, 1, 0, 0, 0, 337, 335, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 341, 1, 0, 0, 0, 339, 337, 1, 0, 0, 0, 340, 342, 3, 26, 13, 0, 341, 340, 1, 0, 0, 0, 341, 342, 1, 0, 0, 0, 342, 345, 1, 0, 0, 0, 343, 344, 5, 32, 0, 0, 344, 346, 3, 26, 13, 0, 345, 343, 1, 0, 0, 0, 345, 346, 1, 0, 0, 0, 346, 45, 1, 0, 0, 0, 347, 348, 5, 4, 0, 0, 348, 349, 3, 26, 13, 0, 349, 47, 1, 0, 0, 0, 350, 352, 5, 18, 0, 0, 351, 353, 3, 26, 13, 0, 352, 351, 1, 0, 0, 0, 352, 353, 1, 0, 0, 0, 353, 356, 1, 0, 0, 0, 354, 355, 5, 32, 0, 0, 355, 357, 3, 26, 13, 0, 356, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 49, 1, 0, 0, 0, 358, 359, 5, 8, 0, 0, 359, 362, 3, 26, 13, 0, 360, 361, 5, 32, 0, 0, 361, 363, 3, 26, 13, 0, 362, 360, 1, 0, 0, 0, 362, 363, 1, 0, 0, 0, 363, 51, 1, 0, 0, 0, 364, 369, 3, 56, 28, 0, 365, 366, 5, 39, 0, 0, 366, 368, 3, 56, 28, 0, 367, 365, 1, 0, 0, 0, 368, 371, 1, 0, 0, 0, 369, 367, 1, 0, 0, 0, 369, 370, 1, 0, 0, 0, 370, 53, 1, 0, 0, 0, 371, 369, 1, 0, 0, 0, 372, 377, 3, 58, 29, 0, 373, 374, 5, 39, 0, 0, 374, 376, 3, 58, 29, 0, 375, 373, 1, 0, 0, 0, 376, 379, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 377, 378, 1, 0, 0, 0, 378, 55, 1, 0, 0, 0, 379, 377, 1, 0, 0, 0, 380, 381, 7, 2, 0, 0, 381, 57, 1, 0, 0, 0, 382, 383, 5, 79, 0, 0, 383, 59, 1, 0, 0, 0, 384, 427, 5, 48, 0, 0, 385, 386, 3, 92, 46, 0, 386, 387, 5, 69, 0, 0, 387, 427, 1, 0, 0, 0, 388, 427, 3, 90, 45, 0, 389, 427, 3, 92, 46, 0, 390, 427, 3, 86, 43, 0, 391, 427, 5, 51, 0, 0, 392, 427, 3, 94, 47, 0, 393, 394, 5, 67, 0, 0, 394, 399, 3, 88, 44, 0, 395, 396, 5, 37, 0, 0, 396, 398, 3, 88, 44, 0, 397, 395, 1, 0, 0, 0, 398, 401, 1, 0, 0, 0, 399, 397, 1, 0, 0, 0, 399, 400, 1, 0, 0, 0, 400, 402, 1, 0, 0, 0, 401, 399, 1, 0, 0, 0, 402, 403, 5, 68, 0, 0, 403, 427, 1, 0, 0, 0, 404, 405, 5, 67, 0, 0, 405, 410, 3, 86, 43, 0, 406, 407, 5, 37, 0, 0, 407, 409, 3, 86, 43, 0, 408, 406, 1, 0, 0, 0, 409, 412, 1, 0, 0, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 413, 1, 0, 0, 0, 412, 410, 1, 0, 0, 0, 413, 414, 5, 68, 0, 0, 414, 427, 1, 0, 0, 0, 415, 416, 5, 67, 0, 0, 416, 421, 3, 94, 47, 0, 417, 418, 5, 37, 0, 0, 418, 420, 3, 94, 47, 0, 419, 417, 1, 0, 0, 0, 420, 423, 1, 0, 0, 0, 421, 419, 1, 0, 0, 0, 421, 422, 1, 0, 0, 0, 422, 424, 1, 0, 0, 0, 423, 421, 1, 0, 0, 0, 424, 425, 5, 68, 0, 0, 425, 427, 1, 0, 0, 0, 426, 384, 1, 0, 0, 0, 426, 385, 1, 0, 0, 0, 426, 388, 1, 0, 0, 0, 426, 389, 1, 0, 0, 0, 426, 390, 1, 0, 0, 0, 426, 391, 1, 0, 0, 0, 426, 392, 1, 0, 0, 0, 426, 393, 1, 0, 0, 0, 426, 404, 1, 0, 0, 0, 426, 415, 1, 0, 0, 0, 427, 61, 1, 0, 0, 0, 428, 429, 5, 10, 0, 0, 429, 430, 5, 30, 0, 0, 430, 63, 1, 0, 0, 0, 431, 432, 5, 17, 0, 0, 432, 437, 3, 66, 33, 0, 433, 434, 5, 37, 0, 0, 434, 436, 3, 66, 33, 0, 435, 433, 1, 0, 0, 0, 436, 439, 1, 0, 0, 0, 437, 435, 1, 0, 0, 0, 437, 438, 1, 0, 0, 0, 438, 65, 1, 0, 0, 0, 439, 437, 1, 0, 0, 0, 440, 442, 3, 10, 5, 0, 441, 443, 7, 3, 0, 0, 442, 441, 1, 0, 0, 0, 442, 443, 1, 0, 0, 0, 443, 446, 1, 0, 0, 0, 444, 445, 5, 49, 0, 0, 445, 447, 7, 4, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 67, 1, 0, 0, 0, 448, 449, 5, 9, 0, 0, 449, 454, 3, 54, 27, 0, 450, 451, 5, 37, 0, 0, 451, 453, 3, 54, 27, 0, 452, 450, 1, 0, 0, 0, 453, 456, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 454, 455, 1, 0, 0, 0, 455, 69, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 457, 458, 5, 2, 0, 0, 458, 463, 3, 54, 27, 0, 459, 460, 5, 37, 0, 0, 460, 462, 3, 54, 27, 0, 461, 459, 1, 0, 0, 0, 462, 465, 1, 0, 0, 0, 463, 461, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 71, 1, 0, 0, 0, 465, 463, 1, 0, 0, 0, 466, 467, 5, 14, 0, 0, 467, 472, 3, 74, 37, 0, 468, 469, 5, 37, 0, 0, 469, 471, 3, 74, 37, 0, 470, 468, 1, 0, 0, 0, 471, 474, 1, 0, 0, 0, 472, 470, 1, 0, 0, 0, 472, 473, 1, 0, 0, 0, 473, 73, 1, 0, 0, 0, 474, 472, 1, 0, 0, 0, 475, 476, 3, 54, 27, 0, 476, 477, 5, 83, 0, 0, 477, 478, 3, 54, 27, 0, 478, 75, 1, 0, 0, 0, 479, 480, 5, 1, 0, 0, 480, 481, 3, 18, 9, 0, 481, 483, 3, 94, 47, 0, 482, 484, 3, 82, 41, 0, 483, 482, 1, 0, 0, 0, 483, 484, 1, 0, 0, 0, 484, 77, 1, 0, 0, 0, 485, 486, 5, 7, 0, 0, 486, 487, 3, 18, 9, 0, 487, 488, 3, 94, 47, 0, 488, 79, 1, 0, 0, 0, 489, 490, 5, 13, 0, 0, 490, 491, 3, 52, 26, 0, 491, 81, 1, 0, 0, 0, 492, 497, 3, 84, 42, 0, 493, 494, 5, 37, 0, 0, 494, 496, 3, 84, 42, 0, 495, 493, 1, 0, 0, 0, 496, 499, 1, 0, 0, 0, 497, 495, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 83, 1, 0, 0, 0, 499, 497, 1, 0, 0, 0, 500, 501, 3, 56, 28, 0, 501, 502, 5, 35, 0, 0, 502, 503, 3, 60, 30, 0, 503, 85, 1, 0, 0, 0, 504, 505, 7, 5, 0, 0, 505, 87, 1, 0, 0, 0, 506, 509, 3, 90, 45, 0, 507, 509, 3, 92, 46, 0, 508, 506, 1, 0, 0, 0, 508, 507, 1, 0, 0, 0, 509, 89, 1, 0, 0, 0, 510, 512, 7, 0, 0, 0, 511, 510, 1, 0, 0, 0, 511, 512, 1, 0, 0, 0, 512, 513, 1, 0, 0, 0, 513, 514, 5, 31, 0, 0, 514, 91, 1, 0, 0, 0, 515, 517, 7, 0, 0, 0, 516, 515, 1, 0, 0, 0, 516, 517, 1, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 519, 5, 30, 0, 0, 519, 93, 1, 0, 0, 0, 520, 521, 5, 29, 0, 0, 521, 95, 1, 0, 0, 0, 522, 523, 7, 6, 0, 0, 523, 97, 1, 0, 0, 0, 524, 525, 5, 5, 0, 0, 525, 526, 3, 100, 50, 0, 526, 99, 1, 0, 0, 0, 527, 528, 5, 67, 0, 0, 528, 529, 3, 2, 1, 0, 529, 530, 5, 68, 0, 0, 530, 101, 1, 0, 0, 0, 531, 532, 5, 16, 0, 0, 532, 533, 5, 99, 0, 0, 533, 103, 1, 0, 0, 0, 534, 535, 5, 11, 0, 0, 535, 536, 5, 103, 0, 0, 536, 105, 1, 0, 0, 0, 537, 538, 5, 3, 0, 0, 538, 541, 5, 89, 0, 0, 539, 540, 5, 87, 0, 0, 540, 542, 3, 54, 27, 0, 541, 539, 1, 0, 0, 0, 541, 542, 1, 0, 0, 0, 542, 552, 1, 0, 0, 0, 543, 544, 5, 88, 0, 0, 544, 549, 3, 108, 54, 0, 545, 546, 5, 37, 0, 0, 546, 548, 3, 108, 54, 0, 547, 545, 1, 0, 0, 0, 548, 551, 1, 0, 0, 0, 549, 547, 1, 0, 0, 0, 549, 550, 1, 0, 0, 0, 550, 553, 1, 0, 0, 0, 551, 549, 1, 0, 0, 0, 552, 543, 1, 0, 0, 0, 552, 553, 1, 0, 0, 0, 553, 107, 1, 0, 0, 0, 554, 555, 3, 54, 27, 0, 555, 556, 5, 35, 0, 0, 556, 558, 1, 0, 0, 0, 557, 554, 1, 0, 0, 0, 557, 558, 1, 0, 0, 0, 558, 559, 1, 0, 0, 0, 559, 560, 3, 54, 27, 0, 560, 109, 1, 0, 0, 0, 55, 121, 130, 145, 157, 166, 174, 178, 186, 188, 193, 200, 205, 212, 218, 226, 228, 239, 246, 257, 260, 274, 282, 290, 294, 297, 307, 316, 324, 337, 341, 345, 352, 356, 362, 369, 377, 399, 410, 421, 426, 437, 442, 446, 454, 463, 472, 483, 497, 508, 511, 516, 541, 549, 552, 557] \ No newline at end of file +[4, 1, 116, 542, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 116, 8, 1, 10, 1, 12, 1, 119, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 127, 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, 3, 3, 142, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 154, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 161, 8, 5, 10, 5, 12, 5, 164, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 171, 8, 5, 1, 5, 1, 5, 3, 5, 175, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 183, 8, 5, 10, 5, 12, 5, 186, 9, 5, 1, 6, 1, 6, 3, 6, 190, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 197, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 202, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 209, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 215, 8, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 223, 8, 8, 10, 8, 12, 8, 226, 9, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 236, 8, 9, 1, 9, 1, 9, 1, 9, 5, 9, 241, 8, 9, 10, 9, 12, 9, 244, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 252, 8, 10, 10, 10, 12, 10, 255, 9, 10, 3, 10, 257, 8, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 269, 8, 13, 10, 13, 12, 13, 272, 9, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 279, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 285, 8, 15, 10, 15, 12, 15, 288, 9, 15, 1, 15, 3, 15, 291, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 3, 17, 297, 8, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 303, 8, 18, 10, 18, 12, 18, 306, 9, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 316, 8, 20, 10, 20, 12, 20, 319, 9, 20, 1, 20, 3, 20, 322, 8, 20, 1, 20, 1, 20, 3, 20, 326, 8, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 3, 22, 333, 8, 22, 1, 22, 1, 22, 3, 22, 337, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 3, 23, 343, 8, 23, 1, 24, 1, 24, 1, 24, 5, 24, 348, 8, 24, 10, 24, 12, 24, 351, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 356, 8, 25, 10, 25, 12, 25, 359, 9, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 5, 28, 378, 8, 28, 10, 28, 12, 28, 381, 9, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 5, 28, 389, 8, 28, 10, 28, 12, 28, 392, 9, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 5, 28, 400, 8, 28, 10, 28, 12, 28, 403, 9, 28, 1, 28, 1, 28, 3, 28, 407, 8, 28, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 416, 8, 30, 10, 30, 12, 30, 419, 9, 30, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 3, 31, 427, 8, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 433, 8, 32, 10, 32, 12, 32, 436, 9, 32, 1, 33, 1, 33, 1, 33, 1, 33, 5, 33, 442, 8, 33, 10, 33, 12, 33, 445, 9, 33, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 451, 8, 34, 10, 34, 12, 34, 454, 9, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 3, 36, 464, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 5, 39, 476, 8, 39, 10, 39, 12, 39, 479, 9, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 3, 42, 489, 8, 42, 1, 43, 3, 43, 492, 8, 43, 1, 43, 1, 43, 1, 44, 3, 44, 497, 8, 44, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 522, 8, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 528, 8, 51, 10, 51, 12, 51, 531, 9, 51, 3, 51, 533, 8, 51, 1, 52, 1, 52, 1, 52, 3, 52, 538, 8, 52, 1, 52, 1, 52, 1, 52, 0, 4, 2, 10, 16, 18, 53, 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, 0, 7, 1, 0, 62, 63, 1, 0, 64, 66, 1, 0, 69, 70, 2, 0, 34, 34, 38, 38, 1, 0, 41, 42, 2, 0, 40, 40, 54, 54, 2, 0, 55, 55, 57, 61, 570, 0, 106, 1, 0, 0, 0, 2, 109, 1, 0, 0, 0, 4, 126, 1, 0, 0, 0, 6, 141, 1, 0, 0, 0, 8, 143, 1, 0, 0, 0, 10, 174, 1, 0, 0, 0, 12, 201, 1, 0, 0, 0, 14, 208, 1, 0, 0, 0, 16, 214, 1, 0, 0, 0, 18, 235, 1, 0, 0, 0, 20, 245, 1, 0, 0, 0, 22, 260, 1, 0, 0, 0, 24, 262, 1, 0, 0, 0, 26, 265, 1, 0, 0, 0, 28, 278, 1, 0, 0, 0, 30, 280, 1, 0, 0, 0, 32, 292, 1, 0, 0, 0, 34, 296, 1, 0, 0, 0, 36, 298, 1, 0, 0, 0, 38, 307, 1, 0, 0, 0, 40, 311, 1, 0, 0, 0, 42, 327, 1, 0, 0, 0, 44, 330, 1, 0, 0, 0, 46, 338, 1, 0, 0, 0, 48, 344, 1, 0, 0, 0, 50, 352, 1, 0, 0, 0, 52, 360, 1, 0, 0, 0, 54, 362, 1, 0, 0, 0, 56, 406, 1, 0, 0, 0, 58, 408, 1, 0, 0, 0, 60, 411, 1, 0, 0, 0, 62, 420, 1, 0, 0, 0, 64, 428, 1, 0, 0, 0, 66, 437, 1, 0, 0, 0, 68, 446, 1, 0, 0, 0, 70, 455, 1, 0, 0, 0, 72, 459, 1, 0, 0, 0, 74, 465, 1, 0, 0, 0, 76, 469, 1, 0, 0, 0, 78, 472, 1, 0, 0, 0, 80, 480, 1, 0, 0, 0, 82, 484, 1, 0, 0, 0, 84, 488, 1, 0, 0, 0, 86, 491, 1, 0, 0, 0, 88, 496, 1, 0, 0, 0, 90, 500, 1, 0, 0, 0, 92, 502, 1, 0, 0, 0, 94, 504, 1, 0, 0, 0, 96, 507, 1, 0, 0, 0, 98, 511, 1, 0, 0, 0, 100, 514, 1, 0, 0, 0, 102, 517, 1, 0, 0, 0, 104, 537, 1, 0, 0, 0, 106, 107, 3, 2, 1, 0, 107, 108, 5, 0, 0, 1, 108, 1, 1, 0, 0, 0, 109, 110, 6, 1, -1, 0, 110, 111, 3, 4, 2, 0, 111, 117, 1, 0, 0, 0, 112, 113, 10, 1, 0, 0, 113, 114, 5, 28, 0, 0, 114, 116, 3, 6, 3, 0, 115, 112, 1, 0, 0, 0, 116, 119, 1, 0, 0, 0, 117, 115, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 3, 1, 0, 0, 0, 119, 117, 1, 0, 0, 0, 120, 127, 3, 94, 47, 0, 121, 127, 3, 30, 15, 0, 122, 127, 3, 24, 12, 0, 123, 127, 3, 40, 20, 0, 124, 127, 3, 98, 49, 0, 125, 127, 3, 100, 50, 0, 126, 120, 1, 0, 0, 0, 126, 121, 1, 0, 0, 0, 126, 122, 1, 0, 0, 0, 126, 123, 1, 0, 0, 0, 126, 124, 1, 0, 0, 0, 126, 125, 1, 0, 0, 0, 127, 5, 1, 0, 0, 0, 128, 142, 3, 42, 21, 0, 129, 142, 3, 46, 23, 0, 130, 142, 3, 58, 29, 0, 131, 142, 3, 64, 32, 0, 132, 142, 3, 60, 30, 0, 133, 142, 3, 44, 22, 0, 134, 142, 3, 8, 4, 0, 135, 142, 3, 66, 33, 0, 136, 142, 3, 68, 34, 0, 137, 142, 3, 72, 36, 0, 138, 142, 3, 74, 37, 0, 139, 142, 3, 102, 51, 0, 140, 142, 3, 76, 38, 0, 141, 128, 1, 0, 0, 0, 141, 129, 1, 0, 0, 0, 141, 130, 1, 0, 0, 0, 141, 131, 1, 0, 0, 0, 141, 132, 1, 0, 0, 0, 141, 133, 1, 0, 0, 0, 141, 134, 1, 0, 0, 0, 141, 135, 1, 0, 0, 0, 141, 136, 1, 0, 0, 0, 141, 137, 1, 0, 0, 0, 141, 138, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 141, 140, 1, 0, 0, 0, 142, 7, 1, 0, 0, 0, 143, 144, 5, 19, 0, 0, 144, 145, 3, 10, 5, 0, 145, 9, 1, 0, 0, 0, 146, 147, 6, 5, -1, 0, 147, 148, 5, 47, 0, 0, 148, 175, 3, 10, 5, 7, 149, 175, 3, 14, 7, 0, 150, 175, 3, 12, 6, 0, 151, 153, 3, 14, 7, 0, 152, 154, 5, 47, 0, 0, 153, 152, 1, 0, 0, 0, 153, 154, 1, 0, 0, 0, 154, 155, 1, 0, 0, 0, 155, 156, 5, 44, 0, 0, 156, 157, 5, 43, 0, 0, 157, 162, 3, 14, 7, 0, 158, 159, 5, 37, 0, 0, 159, 161, 3, 14, 7, 0, 160, 158, 1, 0, 0, 0, 161, 164, 1, 0, 0, 0, 162, 160, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 165, 1, 0, 0, 0, 164, 162, 1, 0, 0, 0, 165, 166, 5, 53, 0, 0, 166, 175, 1, 0, 0, 0, 167, 168, 3, 14, 7, 0, 168, 170, 5, 45, 0, 0, 169, 171, 5, 47, 0, 0, 170, 169, 1, 0, 0, 0, 170, 171, 1, 0, 0, 0, 171, 172, 1, 0, 0, 0, 172, 173, 5, 48, 0, 0, 173, 175, 1, 0, 0, 0, 174, 146, 1, 0, 0, 0, 174, 149, 1, 0, 0, 0, 174, 150, 1, 0, 0, 0, 174, 151, 1, 0, 0, 0, 174, 167, 1, 0, 0, 0, 175, 184, 1, 0, 0, 0, 176, 177, 10, 4, 0, 0, 177, 178, 5, 33, 0, 0, 178, 183, 3, 10, 5, 5, 179, 180, 10, 3, 0, 0, 180, 181, 5, 50, 0, 0, 181, 183, 3, 10, 5, 4, 182, 176, 1, 0, 0, 0, 182, 179, 1, 0, 0, 0, 183, 186, 1, 0, 0, 0, 184, 182, 1, 0, 0, 0, 184, 185, 1, 0, 0, 0, 185, 11, 1, 0, 0, 0, 186, 184, 1, 0, 0, 0, 187, 189, 3, 14, 7, 0, 188, 190, 5, 47, 0, 0, 189, 188, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 191, 1, 0, 0, 0, 191, 192, 5, 46, 0, 0, 192, 193, 3, 90, 45, 0, 193, 202, 1, 0, 0, 0, 194, 196, 3, 14, 7, 0, 195, 197, 5, 47, 0, 0, 196, 195, 1, 0, 0, 0, 196, 197, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 199, 5, 52, 0, 0, 199, 200, 3, 90, 45, 0, 200, 202, 1, 0, 0, 0, 201, 187, 1, 0, 0, 0, 201, 194, 1, 0, 0, 0, 202, 13, 1, 0, 0, 0, 203, 209, 3, 16, 8, 0, 204, 205, 3, 16, 8, 0, 205, 206, 3, 92, 46, 0, 206, 207, 3, 16, 8, 0, 207, 209, 1, 0, 0, 0, 208, 203, 1, 0, 0, 0, 208, 204, 1, 0, 0, 0, 209, 15, 1, 0, 0, 0, 210, 211, 6, 8, -1, 0, 211, 215, 3, 18, 9, 0, 212, 213, 7, 0, 0, 0, 213, 215, 3, 16, 8, 3, 214, 210, 1, 0, 0, 0, 214, 212, 1, 0, 0, 0, 215, 224, 1, 0, 0, 0, 216, 217, 10, 2, 0, 0, 217, 218, 7, 1, 0, 0, 218, 223, 3, 16, 8, 3, 219, 220, 10, 1, 0, 0, 220, 221, 7, 0, 0, 0, 221, 223, 3, 16, 8, 2, 222, 216, 1, 0, 0, 0, 222, 219, 1, 0, 0, 0, 223, 226, 1, 0, 0, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 224, 1, 0, 0, 0, 227, 228, 6, 9, -1, 0, 228, 236, 3, 56, 28, 0, 229, 236, 3, 48, 24, 0, 230, 236, 3, 20, 10, 0, 231, 232, 5, 43, 0, 0, 232, 233, 3, 10, 5, 0, 233, 234, 5, 53, 0, 0, 234, 236, 1, 0, 0, 0, 235, 227, 1, 0, 0, 0, 235, 229, 1, 0, 0, 0, 235, 230, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 236, 242, 1, 0, 0, 0, 237, 238, 10, 1, 0, 0, 238, 239, 5, 36, 0, 0, 239, 241, 3, 22, 11, 0, 240, 237, 1, 0, 0, 0, 241, 244, 1, 0, 0, 0, 242, 240, 1, 0, 0, 0, 242, 243, 1, 0, 0, 0, 243, 19, 1, 0, 0, 0, 244, 242, 1, 0, 0, 0, 245, 246, 3, 52, 26, 0, 246, 256, 5, 43, 0, 0, 247, 257, 5, 64, 0, 0, 248, 253, 3, 10, 5, 0, 249, 250, 5, 37, 0, 0, 250, 252, 3, 10, 5, 0, 251, 249, 1, 0, 0, 0, 252, 255, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 253, 254, 1, 0, 0, 0, 254, 257, 1, 0, 0, 0, 255, 253, 1, 0, 0, 0, 256, 247, 1, 0, 0, 0, 256, 248, 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 259, 5, 53, 0, 0, 259, 21, 1, 0, 0, 0, 260, 261, 3, 52, 26, 0, 261, 23, 1, 0, 0, 0, 262, 263, 5, 15, 0, 0, 263, 264, 3, 26, 13, 0, 264, 25, 1, 0, 0, 0, 265, 270, 3, 28, 14, 0, 266, 267, 5, 37, 0, 0, 267, 269, 3, 28, 14, 0, 268, 266, 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 27, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 279, 3, 10, 5, 0, 274, 275, 3, 48, 24, 0, 275, 276, 5, 35, 0, 0, 276, 277, 3, 10, 5, 0, 277, 279, 1, 0, 0, 0, 278, 273, 1, 0, 0, 0, 278, 274, 1, 0, 0, 0, 279, 29, 1, 0, 0, 0, 280, 281, 5, 6, 0, 0, 281, 286, 3, 32, 16, 0, 282, 283, 5, 37, 0, 0, 283, 285, 3, 32, 16, 0, 284, 282, 1, 0, 0, 0, 285, 288, 1, 0, 0, 0, 286, 284, 1, 0, 0, 0, 286, 287, 1, 0, 0, 0, 287, 290, 1, 0, 0, 0, 288, 286, 1, 0, 0, 0, 289, 291, 3, 34, 17, 0, 290, 289, 1, 0, 0, 0, 290, 291, 1, 0, 0, 0, 291, 31, 1, 0, 0, 0, 292, 293, 5, 24, 0, 0, 293, 33, 1, 0, 0, 0, 294, 297, 3, 36, 18, 0, 295, 297, 3, 38, 19, 0, 296, 294, 1, 0, 0, 0, 296, 295, 1, 0, 0, 0, 297, 35, 1, 0, 0, 0, 298, 299, 5, 74, 0, 0, 299, 304, 3, 32, 16, 0, 300, 301, 5, 37, 0, 0, 301, 303, 3, 32, 16, 0, 302, 300, 1, 0, 0, 0, 303, 306, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 37, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 307, 308, 5, 67, 0, 0, 308, 309, 3, 36, 18, 0, 309, 310, 5, 68, 0, 0, 310, 39, 1, 0, 0, 0, 311, 312, 5, 12, 0, 0, 312, 317, 3, 32, 16, 0, 313, 314, 5, 37, 0, 0, 314, 316, 3, 32, 16, 0, 315, 313, 1, 0, 0, 0, 316, 319, 1, 0, 0, 0, 317, 315, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 321, 1, 0, 0, 0, 319, 317, 1, 0, 0, 0, 320, 322, 3, 26, 13, 0, 321, 320, 1, 0, 0, 0, 321, 322, 1, 0, 0, 0, 322, 325, 1, 0, 0, 0, 323, 324, 5, 32, 0, 0, 324, 326, 3, 26, 13, 0, 325, 323, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 41, 1, 0, 0, 0, 327, 328, 5, 4, 0, 0, 328, 329, 3, 26, 13, 0, 329, 43, 1, 0, 0, 0, 330, 332, 5, 18, 0, 0, 331, 333, 3, 26, 13, 0, 332, 331, 1, 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 336, 1, 0, 0, 0, 334, 335, 5, 32, 0, 0, 335, 337, 3, 26, 13, 0, 336, 334, 1, 0, 0, 0, 336, 337, 1, 0, 0, 0, 337, 45, 1, 0, 0, 0, 338, 339, 5, 8, 0, 0, 339, 342, 3, 26, 13, 0, 340, 341, 5, 32, 0, 0, 341, 343, 3, 26, 13, 0, 342, 340, 1, 0, 0, 0, 342, 343, 1, 0, 0, 0, 343, 47, 1, 0, 0, 0, 344, 349, 3, 52, 26, 0, 345, 346, 5, 39, 0, 0, 346, 348, 3, 52, 26, 0, 347, 345, 1, 0, 0, 0, 348, 351, 1, 0, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 49, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 352, 357, 3, 54, 27, 0, 353, 354, 5, 39, 0, 0, 354, 356, 3, 54, 27, 0, 355, 353, 1, 0, 0, 0, 356, 359, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 357, 358, 1, 0, 0, 0, 358, 51, 1, 0, 0, 0, 359, 357, 1, 0, 0, 0, 360, 361, 7, 2, 0, 0, 361, 53, 1, 0, 0, 0, 362, 363, 5, 78, 0, 0, 363, 55, 1, 0, 0, 0, 364, 407, 5, 48, 0, 0, 365, 366, 3, 88, 44, 0, 366, 367, 5, 69, 0, 0, 367, 407, 1, 0, 0, 0, 368, 407, 3, 86, 43, 0, 369, 407, 3, 88, 44, 0, 370, 407, 3, 82, 41, 0, 371, 407, 5, 51, 0, 0, 372, 407, 3, 90, 45, 0, 373, 374, 5, 67, 0, 0, 374, 379, 3, 84, 42, 0, 375, 376, 5, 37, 0, 0, 376, 378, 3, 84, 42, 0, 377, 375, 1, 0, 0, 0, 378, 381, 1, 0, 0, 0, 379, 377, 1, 0, 0, 0, 379, 380, 1, 0, 0, 0, 380, 382, 1, 0, 0, 0, 381, 379, 1, 0, 0, 0, 382, 383, 5, 68, 0, 0, 383, 407, 1, 0, 0, 0, 384, 385, 5, 67, 0, 0, 385, 390, 3, 82, 41, 0, 386, 387, 5, 37, 0, 0, 387, 389, 3, 82, 41, 0, 388, 386, 1, 0, 0, 0, 389, 392, 1, 0, 0, 0, 390, 388, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 393, 1, 0, 0, 0, 392, 390, 1, 0, 0, 0, 393, 394, 5, 68, 0, 0, 394, 407, 1, 0, 0, 0, 395, 396, 5, 67, 0, 0, 396, 401, 3, 90, 45, 0, 397, 398, 5, 37, 0, 0, 398, 400, 3, 90, 45, 0, 399, 397, 1, 0, 0, 0, 400, 403, 1, 0, 0, 0, 401, 399, 1, 0, 0, 0, 401, 402, 1, 0, 0, 0, 402, 404, 1, 0, 0, 0, 403, 401, 1, 0, 0, 0, 404, 405, 5, 68, 0, 0, 405, 407, 1, 0, 0, 0, 406, 364, 1, 0, 0, 0, 406, 365, 1, 0, 0, 0, 406, 368, 1, 0, 0, 0, 406, 369, 1, 0, 0, 0, 406, 370, 1, 0, 0, 0, 406, 371, 1, 0, 0, 0, 406, 372, 1, 0, 0, 0, 406, 373, 1, 0, 0, 0, 406, 384, 1, 0, 0, 0, 406, 395, 1, 0, 0, 0, 407, 57, 1, 0, 0, 0, 408, 409, 5, 10, 0, 0, 409, 410, 5, 30, 0, 0, 410, 59, 1, 0, 0, 0, 411, 412, 5, 17, 0, 0, 412, 417, 3, 62, 31, 0, 413, 414, 5, 37, 0, 0, 414, 416, 3, 62, 31, 0, 415, 413, 1, 0, 0, 0, 416, 419, 1, 0, 0, 0, 417, 415, 1, 0, 0, 0, 417, 418, 1, 0, 0, 0, 418, 61, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 420, 422, 3, 10, 5, 0, 421, 423, 7, 3, 0, 0, 422, 421, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 426, 1, 0, 0, 0, 424, 425, 5, 49, 0, 0, 425, 427, 7, 4, 0, 0, 426, 424, 1, 0, 0, 0, 426, 427, 1, 0, 0, 0, 427, 63, 1, 0, 0, 0, 428, 429, 5, 9, 0, 0, 429, 434, 3, 50, 25, 0, 430, 431, 5, 37, 0, 0, 431, 433, 3, 50, 25, 0, 432, 430, 1, 0, 0, 0, 433, 436, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 65, 1, 0, 0, 0, 436, 434, 1, 0, 0, 0, 437, 438, 5, 2, 0, 0, 438, 443, 3, 50, 25, 0, 439, 440, 5, 37, 0, 0, 440, 442, 3, 50, 25, 0, 441, 439, 1, 0, 0, 0, 442, 445, 1, 0, 0, 0, 443, 441, 1, 0, 0, 0, 443, 444, 1, 0, 0, 0, 444, 67, 1, 0, 0, 0, 445, 443, 1, 0, 0, 0, 446, 447, 5, 14, 0, 0, 447, 452, 3, 70, 35, 0, 448, 449, 5, 37, 0, 0, 449, 451, 3, 70, 35, 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, 69, 1, 0, 0, 0, 454, 452, 1, 0, 0, 0, 455, 456, 3, 50, 25, 0, 456, 457, 5, 82, 0, 0, 457, 458, 3, 50, 25, 0, 458, 71, 1, 0, 0, 0, 459, 460, 5, 1, 0, 0, 460, 461, 3, 18, 9, 0, 461, 463, 3, 90, 45, 0, 462, 464, 3, 78, 39, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 73, 1, 0, 0, 0, 465, 466, 5, 7, 0, 0, 466, 467, 3, 18, 9, 0, 467, 468, 3, 90, 45, 0, 468, 75, 1, 0, 0, 0, 469, 470, 5, 13, 0, 0, 470, 471, 3, 48, 24, 0, 471, 77, 1, 0, 0, 0, 472, 477, 3, 80, 40, 0, 473, 474, 5, 37, 0, 0, 474, 476, 3, 80, 40, 0, 475, 473, 1, 0, 0, 0, 476, 479, 1, 0, 0, 0, 477, 475, 1, 0, 0, 0, 477, 478, 1, 0, 0, 0, 478, 79, 1, 0, 0, 0, 479, 477, 1, 0, 0, 0, 480, 481, 3, 52, 26, 0, 481, 482, 5, 35, 0, 0, 482, 483, 3, 56, 28, 0, 483, 81, 1, 0, 0, 0, 484, 485, 7, 5, 0, 0, 485, 83, 1, 0, 0, 0, 486, 489, 3, 86, 43, 0, 487, 489, 3, 88, 44, 0, 488, 486, 1, 0, 0, 0, 488, 487, 1, 0, 0, 0, 489, 85, 1, 0, 0, 0, 490, 492, 7, 0, 0, 0, 491, 490, 1, 0, 0, 0, 491, 492, 1, 0, 0, 0, 492, 493, 1, 0, 0, 0, 493, 494, 5, 31, 0, 0, 494, 87, 1, 0, 0, 0, 495, 497, 7, 0, 0, 0, 496, 495, 1, 0, 0, 0, 496, 497, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 499, 5, 30, 0, 0, 499, 89, 1, 0, 0, 0, 500, 501, 5, 29, 0, 0, 501, 91, 1, 0, 0, 0, 502, 503, 7, 6, 0, 0, 503, 93, 1, 0, 0, 0, 504, 505, 5, 5, 0, 0, 505, 506, 3, 96, 48, 0, 506, 95, 1, 0, 0, 0, 507, 508, 5, 67, 0, 0, 508, 509, 3, 2, 1, 0, 509, 510, 5, 68, 0, 0, 510, 97, 1, 0, 0, 0, 511, 512, 5, 16, 0, 0, 512, 513, 5, 98, 0, 0, 513, 99, 1, 0, 0, 0, 514, 515, 5, 11, 0, 0, 515, 516, 5, 102, 0, 0, 516, 101, 1, 0, 0, 0, 517, 518, 5, 3, 0, 0, 518, 521, 5, 88, 0, 0, 519, 520, 5, 86, 0, 0, 520, 522, 3, 50, 25, 0, 521, 519, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 532, 1, 0, 0, 0, 523, 524, 5, 87, 0, 0, 524, 529, 3, 104, 52, 0, 525, 526, 5, 37, 0, 0, 526, 528, 3, 104, 52, 0, 527, 525, 1, 0, 0, 0, 528, 531, 1, 0, 0, 0, 529, 527, 1, 0, 0, 0, 529, 530, 1, 0, 0, 0, 530, 533, 1, 0, 0, 0, 531, 529, 1, 0, 0, 0, 532, 523, 1, 0, 0, 0, 532, 533, 1, 0, 0, 0, 533, 103, 1, 0, 0, 0, 534, 535, 3, 50, 25, 0, 535, 536, 5, 35, 0, 0, 536, 538, 1, 0, 0, 0, 537, 534, 1, 0, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 540, 3, 50, 25, 0, 540, 105, 1, 0, 0, 0, 53, 117, 126, 141, 153, 162, 170, 174, 182, 184, 189, 196, 201, 208, 214, 222, 224, 235, 242, 253, 256, 270, 278, 286, 290, 296, 304, 317, 321, 325, 332, 336, 342, 349, 357, 379, 390, 401, 406, 417, 422, 426, 434, 443, 452, 463, 477, 488, 491, 496, 521, 529, 532, 537] \ 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 7cf25b86ded5c..1d6be711f9814 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 @@ -28,50 +28,50 @@ public class EsqlBaseParser extends Parser { RP=53, TRUE=54, EQ=55, CIEQ=56, NEQ=57, LT=58, LTE=59, GT=60, GTE=61, PLUS=62, MINUS=63, ASTERISK=64, SLASH=65, PERCENT=66, OPENING_BRACKET=67, CLOSING_BRACKET=68, UNQUOTED_IDENTIFIER=69, QUOTED_IDENTIFIER=70, EXPR_LINE_COMMENT=71, - EXPR_MULTILINE_COMMENT=72, EXPR_WS=73, OPTIONS=74, METADATA=75, FROM_LINE_COMMENT=76, - FROM_MULTILINE_COMMENT=77, FROM_WS=78, ID_PATTERN=79, PROJECT_LINE_COMMENT=80, - PROJECT_MULTILINE_COMMENT=81, PROJECT_WS=82, AS=83, RENAME_LINE_COMMENT=84, - RENAME_MULTILINE_COMMENT=85, RENAME_WS=86, ON=87, WITH=88, ENRICH_POLICY_NAME=89, - ENRICH_LINE_COMMENT=90, ENRICH_MULTILINE_COMMENT=91, ENRICH_WS=92, ENRICH_FIELD_LINE_COMMENT=93, - ENRICH_FIELD_MULTILINE_COMMENT=94, ENRICH_FIELD_WS=95, MVEXPAND_LINE_COMMENT=96, - MVEXPAND_MULTILINE_COMMENT=97, MVEXPAND_WS=98, INFO=99, SHOW_LINE_COMMENT=100, - SHOW_MULTILINE_COMMENT=101, SHOW_WS=102, FUNCTIONS=103, META_LINE_COMMENT=104, - META_MULTILINE_COMMENT=105, META_WS=106, COLON=107, SETTING=108, SETTING_LINE_COMMENT=109, - SETTTING_MULTILINE_COMMENT=110, SETTING_WS=111, METRICS_LINE_COMMENT=112, - METRICS_MULTILINE_COMMENT=113, METRICS_WS=114, CLOSING_METRICS_LINE_COMMENT=115, - CLOSING_METRICS_MULTILINE_COMMENT=116, CLOSING_METRICS_WS=117; + EXPR_MULTILINE_COMMENT=72, EXPR_WS=73, METADATA=74, FROM_LINE_COMMENT=75, + FROM_MULTILINE_COMMENT=76, FROM_WS=77, ID_PATTERN=78, PROJECT_LINE_COMMENT=79, + PROJECT_MULTILINE_COMMENT=80, PROJECT_WS=81, AS=82, RENAME_LINE_COMMENT=83, + RENAME_MULTILINE_COMMENT=84, RENAME_WS=85, ON=86, WITH=87, ENRICH_POLICY_NAME=88, + ENRICH_LINE_COMMENT=89, ENRICH_MULTILINE_COMMENT=90, ENRICH_WS=91, ENRICH_FIELD_LINE_COMMENT=92, + ENRICH_FIELD_MULTILINE_COMMENT=93, ENRICH_FIELD_WS=94, MVEXPAND_LINE_COMMENT=95, + MVEXPAND_MULTILINE_COMMENT=96, MVEXPAND_WS=97, INFO=98, SHOW_LINE_COMMENT=99, + SHOW_MULTILINE_COMMENT=100, SHOW_WS=101, FUNCTIONS=102, META_LINE_COMMENT=103, + META_MULTILINE_COMMENT=104, META_WS=105, COLON=106, SETTING=107, SETTING_LINE_COMMENT=108, + SETTTING_MULTILINE_COMMENT=109, SETTING_WS=110, METRICS_LINE_COMMENT=111, + METRICS_MULTILINE_COMMENT=112, METRICS_WS=113, CLOSING_METRICS_LINE_COMMENT=114, + CLOSING_METRICS_MULTILINE_COMMENT=115, CLOSING_METRICS_WS=116; public static final int RULE_singleStatement = 0, RULE_query = 1, RULE_sourceCommand = 2, RULE_processingCommand = 3, RULE_whereCommand = 4, RULE_booleanExpression = 5, RULE_regexBooleanExpression = 6, RULE_valueExpression = 7, RULE_operatorExpression = 8, RULE_primaryExpression = 9, RULE_functionExpression = 10, RULE_dataType = 11, RULE_rowCommand = 12, RULE_fields = 13, RULE_field = 14, RULE_fromCommand = 15, RULE_indexIdentifier = 16, - RULE_fromOptions = 17, RULE_configOption = 18, RULE_metadata = 19, RULE_metadataOption = 20, - RULE_deprecated_metadata = 21, RULE_metricsCommand = 22, RULE_evalCommand = 23, - RULE_statsCommand = 24, RULE_inlinestatsCommand = 25, RULE_qualifiedName = 26, - RULE_qualifiedNamePattern = 27, RULE_identifier = 28, RULE_identifierPattern = 29, - RULE_constant = 30, RULE_limitCommand = 31, RULE_sortCommand = 32, RULE_orderExpression = 33, - RULE_keepCommand = 34, RULE_dropCommand = 35, RULE_renameCommand = 36, - RULE_renameClause = 37, RULE_dissectCommand = 38, RULE_grokCommand = 39, - RULE_mvExpandCommand = 40, RULE_commandOptions = 41, RULE_commandOption = 42, - RULE_booleanValue = 43, RULE_numericValue = 44, RULE_decimalValue = 45, - RULE_integerValue = 46, RULE_string = 47, RULE_comparisonOperator = 48, - RULE_explainCommand = 49, RULE_subqueryExpression = 50, RULE_showCommand = 51, - RULE_metaCommand = 52, RULE_enrichCommand = 53, RULE_enrichWithClause = 54; + RULE_metadata = 17, RULE_metadataOption = 18, RULE_deprecated_metadata = 19, + RULE_metricsCommand = 20, RULE_evalCommand = 21, RULE_statsCommand = 22, + RULE_inlinestatsCommand = 23, RULE_qualifiedName = 24, RULE_qualifiedNamePattern = 25, + RULE_identifier = 26, RULE_identifierPattern = 27, RULE_constant = 28, + RULE_limitCommand = 29, RULE_sortCommand = 30, RULE_orderExpression = 31, + RULE_keepCommand = 32, RULE_dropCommand = 33, RULE_renameCommand = 34, + RULE_renameClause = 35, RULE_dissectCommand = 36, RULE_grokCommand = 37, + RULE_mvExpandCommand = 38, RULE_commandOptions = 39, RULE_commandOption = 40, + RULE_booleanValue = 41, RULE_numericValue = 42, RULE_decimalValue = 43, + RULE_integerValue = 44, RULE_string = 45, RULE_comparisonOperator = 46, + RULE_explainCommand = 47, RULE_subqueryExpression = 48, RULE_showCommand = 49, + RULE_metaCommand = 50, RULE_enrichCommand = 51, RULE_enrichWithClause = 52; private static String[] makeRuleNames() { return new String[] { "singleStatement", "query", "sourceCommand", "processingCommand", "whereCommand", "booleanExpression", "regexBooleanExpression", "valueExpression", "operatorExpression", "primaryExpression", "functionExpression", "dataType", "rowCommand", - "fields", "field", "fromCommand", "indexIdentifier", "fromOptions", "configOption", - "metadata", "metadataOption", "deprecated_metadata", "metricsCommand", - "evalCommand", "statsCommand", "inlinestatsCommand", "qualifiedName", - "qualifiedNamePattern", "identifier", "identifierPattern", "constant", - "limitCommand", "sortCommand", "orderExpression", "keepCommand", "dropCommand", - "renameCommand", "renameClause", "dissectCommand", "grokCommand", "mvExpandCommand", - "commandOptions", "commandOption", "booleanValue", "numericValue", "decimalValue", - "integerValue", "string", "comparisonOperator", "explainCommand", "subqueryExpression", - "showCommand", "metaCommand", "enrichCommand", "enrichWithClause" + "fields", "field", "fromCommand", "indexIdentifier", "metadata", "metadataOption", + "deprecated_metadata", "metricsCommand", "evalCommand", "statsCommand", + "inlinestatsCommand", "qualifiedName", "qualifiedNamePattern", "identifier", + "identifierPattern", "constant", "limitCommand", "sortCommand", "orderExpression", + "keepCommand", "dropCommand", "renameCommand", "renameClause", "dissectCommand", + "grokCommand", "mvExpandCommand", "commandOptions", "commandOption", + "booleanValue", "numericValue", "decimalValue", "integerValue", "string", + "comparisonOperator", "explainCommand", "subqueryExpression", "showCommand", + "metaCommand", "enrichCommand", "enrichWithClause" }; } public static final String[] ruleNames = makeRuleNames(); @@ -86,10 +86,10 @@ private static String[] makeLiteralNames() { "'first'", "'last'", "'('", "'in'", "'is'", "'like'", "'not'", "'null'", "'nulls'", "'or'", "'?'", "'rlike'", "')'", "'true'", "'=='", "'=~'", "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", - null, "']'", null, null, null, null, null, "'options'", "'metadata'", - null, null, null, null, null, null, null, "'as'", null, null, null, "'on'", - "'with'", null, null, null, null, null, null, null, null, null, null, - "'info'", null, null, null, "'functions'", null, null, null, "':'" + null, "']'", null, null, null, null, null, "'metadata'", null, null, + null, null, null, null, null, "'as'", null, null, null, "'on'", "'with'", + null, null, null, null, null, null, null, null, null, null, "'info'", + null, null, null, "'functions'", null, null, null, "':'" }; } private static final String[] _LITERAL_NAMES = makeLiteralNames(); @@ -106,17 +106,17 @@ private static String[] makeSymbolicNames() { "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", - "OPTIONS", "METADATA", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", - "FROM_WS", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", - "PROJECT_WS", "AS", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", - "RENAME_WS", "ON", "WITH", "ENRICH_POLICY_NAME", "ENRICH_LINE_COMMENT", - "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", - "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "MVEXPAND_LINE_COMMENT", - "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", "INFO", "SHOW_LINE_COMMENT", - "SHOW_MULTILINE_COMMENT", "SHOW_WS", "FUNCTIONS", "META_LINE_COMMENT", - "META_MULTILINE_COMMENT", "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", - "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", - "METRICS_WS", "CLOSING_METRICS_LINE_COMMENT", "CLOSING_METRICS_MULTILINE_COMMENT", + "METADATA", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", "FROM_WS", + "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", + "AS", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", + "ON", "WITH", "ENRICH_POLICY_NAME", "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", + "ENRICH_WS", "ENRICH_FIELD_LINE_COMMENT", "ENRICH_FIELD_MULTILINE_COMMENT", + "ENRICH_FIELD_WS", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", + "MVEXPAND_WS", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", + "SHOW_WS", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", + "META_WS", "COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", + "SETTING_WS", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", + "CLOSING_METRICS_LINE_COMMENT", "CLOSING_METRICS_MULTILINE_COMMENT", "CLOSING_METRICS_WS" }; } @@ -204,9 +204,9 @@ public final SingleStatementContext singleStatement() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(110); + setState(106); query(0); - setState(111); + setState(107); match(EOF); } } @@ -302,11 +302,11 @@ private QueryContext query(int _p) throws RecognitionException { _ctx = _localctx; _prevctx = _localctx; - setState(114); + setState(110); sourceCommand(); } _ctx.stop = _input.LT(-1); - setState(121); + setState(117); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,0,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -317,16 +317,16 @@ private QueryContext query(int _p) throws RecognitionException { { _localctx = new CompositeQueryContext(new QueryContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_query); - setState(116); + setState(112); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(117); + setState(113); match(PIPE); - setState(118); + setState(114); processingCommand(); } } } - setState(123); + setState(119); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,0,_ctx); } @@ -387,48 +387,48 @@ public final SourceCommandContext sourceCommand() throws RecognitionException { SourceCommandContext _localctx = new SourceCommandContext(_ctx, getState()); enterRule(_localctx, 4, RULE_sourceCommand); try { - setState(130); + setState(126); _errHandler.sync(this); switch (_input.LA(1)) { case EXPLAIN: enterOuterAlt(_localctx, 1); { - setState(124); + setState(120); explainCommand(); } break; case FROM: enterOuterAlt(_localctx, 2); { - setState(125); + setState(121); fromCommand(); } break; case ROW: enterOuterAlt(_localctx, 3); { - setState(126); + setState(122); rowCommand(); } break; case METRICS: enterOuterAlt(_localctx, 4); { - setState(127); + setState(123); metricsCommand(); } break; case SHOW: enterOuterAlt(_localctx, 5); { - setState(128); + setState(124); showCommand(); } break; case META: enterOuterAlt(_localctx, 6); { - setState(129); + setState(125); metaCommand(); } break; @@ -512,97 +512,97 @@ public final ProcessingCommandContext processingCommand() throws RecognitionExce ProcessingCommandContext _localctx = new ProcessingCommandContext(_ctx, getState()); enterRule(_localctx, 6, RULE_processingCommand); try { - setState(145); + setState(141); _errHandler.sync(this); switch (_input.LA(1)) { case EVAL: enterOuterAlt(_localctx, 1); { - setState(132); + setState(128); evalCommand(); } break; case INLINESTATS: enterOuterAlt(_localctx, 2); { - setState(133); + setState(129); inlinestatsCommand(); } break; case LIMIT: enterOuterAlt(_localctx, 3); { - setState(134); + setState(130); limitCommand(); } break; case KEEP: enterOuterAlt(_localctx, 4); { - setState(135); + setState(131); keepCommand(); } break; case SORT: enterOuterAlt(_localctx, 5); { - setState(136); + setState(132); sortCommand(); } break; case STATS: enterOuterAlt(_localctx, 6); { - setState(137); + setState(133); statsCommand(); } break; case WHERE: enterOuterAlt(_localctx, 7); { - setState(138); + setState(134); whereCommand(); } break; case DROP: enterOuterAlt(_localctx, 8); { - setState(139); + setState(135); dropCommand(); } break; case RENAME: enterOuterAlt(_localctx, 9); { - setState(140); + setState(136); renameCommand(); } break; case DISSECT: enterOuterAlt(_localctx, 10); { - setState(141); + setState(137); dissectCommand(); } break; case GROK: enterOuterAlt(_localctx, 11); { - setState(142); + setState(138); grokCommand(); } break; case ENRICH: enterOuterAlt(_localctx, 12); { - setState(143); + setState(139); enrichCommand(); } break; case MV_EXPAND: enterOuterAlt(_localctx, 13); { - setState(144); + setState(140); mvExpandCommand(); } break; @@ -653,9 +653,9 @@ public final WhereCommandContext whereCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(147); + setState(143); match(WHERE); - setState(148); + setState(144); booleanExpression(0); } } @@ -850,7 +850,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(178); + setState(174); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { case 1: @@ -859,9 +859,9 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(151); + setState(147); match(NOT); - setState(152); + setState(148); booleanExpression(7); } break; @@ -870,7 +870,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new BooleanDefaultContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(153); + setState(149); valueExpression(); } break; @@ -879,7 +879,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new RegexExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(154); + setState(150); regexBooleanExpression(); } break; @@ -888,41 +888,41 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new LogicalInContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(155); + setState(151); valueExpression(); - setState(157); + setState(153); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(156); + setState(152); match(NOT); } } - setState(159); + setState(155); match(IN); - setState(160); + setState(156); match(LP); - setState(161); + setState(157); valueExpression(); - setState(166); + setState(162); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(162); + setState(158); match(COMMA); - setState(163); + setState(159); valueExpression(); } } - setState(168); + setState(164); _errHandler.sync(this); _la = _input.LA(1); } - setState(169); + setState(165); match(RP); } break; @@ -931,27 +931,27 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc _localctx = new IsNullContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(171); + setState(167); valueExpression(); - setState(172); + setState(168); match(IS); - setState(174); + setState(170); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(173); + setState(169); match(NOT); } } - setState(176); + setState(172); match(NULL); } break; } _ctx.stop = _input.LT(-1); - setState(188); + setState(184); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,8,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -959,7 +959,7 @@ private BooleanExpressionContext booleanExpression(int _p) throws RecognitionExc if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(186); + setState(182); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,7,_ctx) ) { case 1: @@ -967,11 +967,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(180); + setState(176); if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(181); + setState(177); ((LogicalBinaryContext)_localctx).operator = match(AND); - setState(182); + setState(178); ((LogicalBinaryContext)_localctx).right = booleanExpression(5); } break; @@ -980,18 +980,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(183); + setState(179); if (!(precpred(_ctx, 3))) throw new FailedPredicateException(this, "precpred(_ctx, 3)"); - setState(184); + setState(180); ((LogicalBinaryContext)_localctx).operator = match(OR); - setState(185); + setState(181); ((LogicalBinaryContext)_localctx).right = booleanExpression(4); } break; } } } - setState(190); + setState(186); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,8,_ctx); } @@ -1046,48 +1046,48 @@ public final RegexBooleanExpressionContext regexBooleanExpression() throws Recog enterRule(_localctx, 12, RULE_regexBooleanExpression); int _la; try { - setState(205); + setState(201); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,11,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(191); + setState(187); valueExpression(); - setState(193); + setState(189); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(192); + setState(188); match(NOT); } } - setState(195); + setState(191); ((RegexBooleanExpressionContext)_localctx).kind = match(LIKE); - setState(196); + setState(192); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(198); + setState(194); valueExpression(); - setState(200); + setState(196); _errHandler.sync(this); _la = _input.LA(1); if (_la==NOT) { { - setState(199); + setState(195); match(NOT); } } - setState(202); + setState(198); ((RegexBooleanExpressionContext)_localctx).kind = match(RLIKE); - setState(203); + setState(199); ((RegexBooleanExpressionContext)_localctx).pattern = string(); } break; @@ -1173,14 +1173,14 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio ValueExpressionContext _localctx = new ValueExpressionContext(_ctx, getState()); enterRule(_localctx, 14, RULE_valueExpression); try { - setState(212); + setState(208); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) { case 1: _localctx = new ValueExpressionDefaultContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(207); + setState(203); operatorExpression(0); } break; @@ -1188,11 +1188,11 @@ public final ValueExpressionContext valueExpression() throws RecognitionExceptio _localctx = new ComparisonContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(208); + setState(204); ((ComparisonContext)_localctx).left = operatorExpression(0); - setState(209); + setState(205); comparisonOperator(); - setState(210); + setState(206); ((ComparisonContext)_localctx).right = operatorExpression(0); } break; @@ -1317,7 +1317,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE int _alt; enterOuterAlt(_localctx, 1); { - setState(218); + setState(214); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,13,_ctx) ) { case 1: @@ -1326,7 +1326,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _ctx = _localctx; _prevctx = _localctx; - setState(215); + setState(211); primaryExpression(0); } break; @@ -1335,7 +1335,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _localctx = new ArithmeticUnaryContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(216); + setState(212); ((ArithmeticUnaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -1346,13 +1346,13 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(217); + setState(213); operatorExpression(3); } break; } _ctx.stop = _input.LT(-1); - setState(228); + setState(224); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,15,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -1360,7 +1360,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(226); + setState(222); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) { case 1: @@ -1368,9 +1368,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(220); + setState(216); if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(221); + setState(217); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(((((_la - 64)) & ~0x3f) == 0 && ((1L << (_la - 64)) & 7L) != 0)) ) { @@ -1381,7 +1381,7 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(222); + setState(218); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(3); } break; @@ -1390,9 +1390,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(223); + setState(219); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(224); + setState(220); ((ArithmeticBinaryContext)_localctx).operator = _input.LT(1); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { @@ -1403,14 +1403,14 @@ private OperatorExpressionContext operatorExpression(int _p) throws RecognitionE _errHandler.reportMatch(this); consume(); } - setState(225); + setState(221); ((ArithmeticBinaryContext)_localctx).right = operatorExpression(2); } break; } } } - setState(230); + setState(226); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,15,_ctx); } @@ -1568,7 +1568,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc int _alt; enterOuterAlt(_localctx, 1); { - setState(239); + setState(235); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,16,_ctx) ) { case 1: @@ -1577,7 +1577,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _ctx = _localctx; _prevctx = _localctx; - setState(232); + setState(228); constant(); } break; @@ -1586,7 +1586,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new DereferenceContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(233); + setState(229); qualifiedName(); } break; @@ -1595,7 +1595,7 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new FunctionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(234); + setState(230); functionExpression(); } break; @@ -1604,17 +1604,17 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc _localctx = new ParenthesizedExpressionContext(_localctx); _ctx = _localctx; _prevctx = _localctx; - setState(235); + setState(231); match(LP); - setState(236); + setState(232); booleanExpression(0); - setState(237); + setState(233); match(RP); } break; } _ctx.stop = _input.LT(-1); - setState(246); + setState(242); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,17,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { @@ -1625,16 +1625,16 @@ private PrimaryExpressionContext primaryExpression(int _p) throws RecognitionExc { _localctx = new InlineCastContext(new PrimaryExpressionContext(_parentctx, _parentState)); pushNewRecursionContext(_localctx, _startState, RULE_primaryExpression); - setState(241); + setState(237); if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(242); + setState(238); match(CAST_OP); - setState(243); + setState(239); dataType(); } } } - setState(248); + setState(244); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,17,_ctx); } @@ -1696,16 +1696,16 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx try { enterOuterAlt(_localctx, 1); { - setState(249); + setState(245); identifier(); - setState(250); + setState(246); match(LP); - setState(260); + setState(256); _errHandler.sync(this); switch (_input.LA(1)) { case ASTERISK: { - setState(251); + setState(247); match(ASTERISK); } break; @@ -1725,21 +1725,21 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx case QUOTED_IDENTIFIER: { { - setState(252); + setState(248); booleanExpression(0); - setState(257); + setState(253); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(253); + setState(249); match(COMMA); - setState(254); + setState(250); booleanExpression(0); } } - setState(259); + setState(255); _errHandler.sync(this); _la = _input.LA(1); } @@ -1751,7 +1751,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx default: break; } - setState(262); + setState(258); match(RP); } } @@ -1809,7 +1809,7 @@ public final DataTypeContext dataType() throws RecognitionException { _localctx = new ToDataTypeContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(264); + setState(260); identifier(); } } @@ -1856,9 +1856,9 @@ public final RowCommandContext rowCommand() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(266); + setState(262); match(ROW); - setState(267); + setState(263); fields(); } } @@ -1912,23 +1912,23 @@ public final FieldsContext fields() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(269); + setState(265); field(); - setState(274); + setState(270); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,20,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(270); + setState(266); match(COMMA); - setState(271); + setState(267); field(); } } } - setState(276); + setState(272); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,20,_ctx); } @@ -1978,24 +1978,24 @@ public final FieldContext field() throws RecognitionException { FieldContext _localctx = new FieldContext(_ctx, getState()); enterRule(_localctx, 28, RULE_field); try { - setState(282); + setState(278); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,21,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(277); + setState(273); booleanExpression(0); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(278); + setState(274); qualifiedName(); - setState(279); + setState(275); match(ASSIGN); - setState(280); + setState(276); booleanExpression(0); } break; @@ -2028,9 +2028,6 @@ public TerminalNode COMMA(int i) { public MetadataContext metadata() { return getRuleContext(MetadataContext.class,0); } - public FromOptionsContext fromOptions() { - return getRuleContext(FromOptionsContext.class,0); - } @SuppressWarnings("this-escape") public FromCommandContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); @@ -2058,48 +2055,38 @@ public final FromCommandContext fromCommand() throws RecognitionException { int _alt; enterOuterAlt(_localctx, 1); { - setState(284); + setState(280); match(FROM); - setState(285); + setState(281); indexIdentifier(); - setState(290); + setState(286); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,22,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(286); + setState(282); match(COMMA); - setState(287); + setState(283); indexIdentifier(); } } } - setState(292); + setState(288); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,22,_ctx); } - setState(294); + setState(290); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,23,_ctx) ) { case 1: { - setState(293); + setState(289); metadata(); } break; } - setState(297); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,24,_ctx) ) { - case 1: - { - setState(296); - fromOptions(); - } - break; - } } } catch (RecognitionException re) { @@ -2142,7 +2129,7 @@ public final IndexIdentifierContext indexIdentifier() throws RecognitionExceptio try { enterOuterAlt(_localctx, 1); { - setState(299); + setState(292); match(INDEX_UNQUOTED_IDENTIFIER); } } @@ -2157,135 +2144,6 @@ public final IndexIdentifierContext indexIdentifier() throws RecognitionExceptio return _localctx; } - @SuppressWarnings("CheckReturnValue") - public static class FromOptionsContext extends ParserRuleContext { - public TerminalNode OPTIONS() { return getToken(EsqlBaseParser.OPTIONS, 0); } - public List configOption() { - return getRuleContexts(ConfigOptionContext.class); - } - public ConfigOptionContext configOption(int i) { - return getRuleContext(ConfigOptionContext.class,i); - } - public List COMMA() { return getTokens(EsqlBaseParser.COMMA); } - public TerminalNode COMMA(int i) { - return getToken(EsqlBaseParser.COMMA, i); - } - @SuppressWarnings("this-escape") - public FromOptionsContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_fromOptions; } - @Override - public void enterRule(ParseTreeListener listener) { - if ( listener instanceof EsqlBaseParserListener ) ((EsqlBaseParserListener)listener).enterFromOptions(this); - } - @Override - public void exitRule(ParseTreeListener listener) { - if ( listener instanceof EsqlBaseParserListener ) ((EsqlBaseParserListener)listener).exitFromOptions(this); - } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof EsqlBaseParserVisitor ) return ((EsqlBaseParserVisitor)visitor).visitFromOptions(this); - else return visitor.visitChildren(this); - } - } - - public final FromOptionsContext fromOptions() throws RecognitionException { - FromOptionsContext _localctx = new FromOptionsContext(_ctx, getState()); - enterRule(_localctx, 34, RULE_fromOptions); - try { - int _alt; - enterOuterAlt(_localctx, 1); - { - setState(301); - match(OPTIONS); - setState(302); - configOption(); - setState(307); - _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,25,_ctx); - while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { - if ( _alt==1 ) { - { - { - setState(303); - match(COMMA); - setState(304); - configOption(); - } - } - } - setState(309); - _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,25,_ctx); - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - @SuppressWarnings("CheckReturnValue") - public static class ConfigOptionContext extends ParserRuleContext { - public List string() { - return getRuleContexts(StringContext.class); - } - public StringContext string(int i) { - return getRuleContext(StringContext.class,i); - } - public TerminalNode ASSIGN() { return getToken(EsqlBaseParser.ASSIGN, 0); } - @SuppressWarnings("this-escape") - public ConfigOptionContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_configOption; } - @Override - public void enterRule(ParseTreeListener listener) { - if ( listener instanceof EsqlBaseParserListener ) ((EsqlBaseParserListener)listener).enterConfigOption(this); - } - @Override - public void exitRule(ParseTreeListener listener) { - if ( listener instanceof EsqlBaseParserListener ) ((EsqlBaseParserListener)listener).exitConfigOption(this); - } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof EsqlBaseParserVisitor ) return ((EsqlBaseParserVisitor)visitor).visitConfigOption(this); - else return visitor.visitChildren(this); - } - } - - public final ConfigOptionContext configOption() throws RecognitionException { - ConfigOptionContext _localctx = new ConfigOptionContext(_ctx, getState()); - enterRule(_localctx, 36, RULE_configOption); - try { - enterOuterAlt(_localctx, 1); - { - setState(310); - string(); - setState(311); - match(ASSIGN); - setState(312); - string(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - @SuppressWarnings("CheckReturnValue") public static class MetadataContext extends ParserRuleContext { public MetadataOptionContext metadataOption() { @@ -2316,22 +2174,22 @@ public T accept(ParseTreeVisitor visitor) { public final MetadataContext metadata() throws RecognitionException { MetadataContext _localctx = new MetadataContext(_ctx, getState()); - enterRule(_localctx, 38, RULE_metadata); + enterRule(_localctx, 34, RULE_metadata); try { - setState(316); + setState(296); _errHandler.sync(this); switch (_input.LA(1)) { case METADATA: enterOuterAlt(_localctx, 1); { - setState(314); + setState(294); metadataOption(); } break; case OPENING_BRACKET: enterOuterAlt(_localctx, 2); { - setState(315); + setState(295); deprecated_metadata(); } break; @@ -2385,32 +2243,32 @@ public T accept(ParseTreeVisitor visitor) { public final MetadataOptionContext metadataOption() throws RecognitionException { MetadataOptionContext _localctx = new MetadataOptionContext(_ctx, getState()); - enterRule(_localctx, 40, RULE_metadataOption); + enterRule(_localctx, 36, RULE_metadataOption); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(318); + setState(298); match(METADATA); - setState(319); + setState(299); indexIdentifier(); - setState(324); + setState(304); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,27,_ctx); + _alt = getInterpreter().adaptivePredict(_input,25,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(320); + setState(300); match(COMMA); - setState(321); + setState(301); indexIdentifier(); } } } - setState(326); + setState(306); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,27,_ctx); + _alt = getInterpreter().adaptivePredict(_input,25,_ctx); } } } @@ -2453,15 +2311,15 @@ public T accept(ParseTreeVisitor visitor) { public final Deprecated_metadataContext deprecated_metadata() throws RecognitionException { Deprecated_metadataContext _localctx = new Deprecated_metadataContext(_ctx, getState()); - enterRule(_localctx, 42, RULE_deprecated_metadata); + enterRule(_localctx, 38, RULE_deprecated_metadata); try { enterOuterAlt(_localctx, 1); { - setState(327); + setState(307); match(OPENING_BRACKET); - setState(328); + setState(308); metadataOption(); - setState(329); + setState(309); match(CLOSING_BRACKET); } } @@ -2520,51 +2378,51 @@ public T accept(ParseTreeVisitor visitor) { public final MetricsCommandContext metricsCommand() throws RecognitionException { MetricsCommandContext _localctx = new MetricsCommandContext(_ctx, getState()); - enterRule(_localctx, 44, RULE_metricsCommand); + enterRule(_localctx, 40, RULE_metricsCommand); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(331); + setState(311); match(METRICS); - setState(332); + setState(312); indexIdentifier(); - setState(337); + setState(317); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,28,_ctx); + _alt = getInterpreter().adaptivePredict(_input,26,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(333); + setState(313); match(COMMA); - setState(334); + setState(314); indexIdentifier(); } } } - setState(339); + setState(319); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,28,_ctx); + _alt = getInterpreter().adaptivePredict(_input,26,_ctx); } - setState(341); + setState(321); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,29,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,27,_ctx) ) { case 1: { - setState(340); + setState(320); ((MetricsCommandContext)_localctx).aggregates = fields(); } break; } - setState(345); + setState(325); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,30,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,28,_ctx) ) { case 1: { - setState(343); + setState(323); match(BY); - setState(344); + setState(324); ((MetricsCommandContext)_localctx).grouping = fields(); } break; @@ -2610,13 +2468,13 @@ public T accept(ParseTreeVisitor visitor) { public final EvalCommandContext evalCommand() throws RecognitionException { EvalCommandContext _localctx = new EvalCommandContext(_ctx, getState()); - enterRule(_localctx, 46, RULE_evalCommand); + enterRule(_localctx, 42, RULE_evalCommand); try { enterOuterAlt(_localctx, 1); { - setState(347); + setState(327); match(EVAL); - setState(348); + setState(328); fields(); } } @@ -2665,30 +2523,30 @@ public T accept(ParseTreeVisitor visitor) { public final StatsCommandContext statsCommand() throws RecognitionException { StatsCommandContext _localctx = new StatsCommandContext(_ctx, getState()); - enterRule(_localctx, 48, RULE_statsCommand); + enterRule(_localctx, 44, RULE_statsCommand); try { enterOuterAlt(_localctx, 1); { - setState(350); + setState(330); match(STATS); - setState(352); + setState(332); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,31,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,29,_ctx) ) { case 1: { - setState(351); + setState(331); ((StatsCommandContext)_localctx).stats = fields(); } break; } - setState(356); + setState(336); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,32,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,30,_ctx) ) { case 1: { - setState(354); + setState(334); match(BY); - setState(355); + setState(335); ((StatsCommandContext)_localctx).grouping = fields(); } break; @@ -2740,22 +2598,22 @@ public T accept(ParseTreeVisitor visitor) { public final InlinestatsCommandContext inlinestatsCommand() throws RecognitionException { InlinestatsCommandContext _localctx = new InlinestatsCommandContext(_ctx, getState()); - enterRule(_localctx, 50, RULE_inlinestatsCommand); + enterRule(_localctx, 46, RULE_inlinestatsCommand); try { enterOuterAlt(_localctx, 1); { - setState(358); + setState(338); match(INLINESTATS); - setState(359); + setState(339); ((InlinestatsCommandContext)_localctx).stats = fields(); - setState(362); + setState(342); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,33,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,31,_ctx) ) { case 1: { - setState(360); + setState(340); match(BY); - setState(361); + setState(341); ((InlinestatsCommandContext)_localctx).grouping = fields(); } break; @@ -2807,30 +2665,30 @@ public T accept(ParseTreeVisitor visitor) { public final QualifiedNameContext qualifiedName() throws RecognitionException { QualifiedNameContext _localctx = new QualifiedNameContext(_ctx, getState()); - enterRule(_localctx, 52, RULE_qualifiedName); + enterRule(_localctx, 48, RULE_qualifiedName); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(364); + setState(344); identifier(); - setState(369); + setState(349); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,34,_ctx); + _alt = getInterpreter().adaptivePredict(_input,32,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(365); + setState(345); match(DOT); - setState(366); + setState(346); identifier(); } } } - setState(371); + setState(351); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,34,_ctx); + _alt = getInterpreter().adaptivePredict(_input,32,_ctx); } } } @@ -2879,30 +2737,30 @@ public T accept(ParseTreeVisitor visitor) { public final QualifiedNamePatternContext qualifiedNamePattern() throws RecognitionException { QualifiedNamePatternContext _localctx = new QualifiedNamePatternContext(_ctx, getState()); - enterRule(_localctx, 54, RULE_qualifiedNamePattern); + enterRule(_localctx, 50, RULE_qualifiedNamePattern); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(372); + setState(352); identifierPattern(); - setState(377); + setState(357); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,35,_ctx); + _alt = getInterpreter().adaptivePredict(_input,33,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(373); + setState(353); match(DOT); - setState(374); + setState(354); identifierPattern(); } } } - setState(379); + setState(359); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,35,_ctx); + _alt = getInterpreter().adaptivePredict(_input,33,_ctx); } } } @@ -2943,12 +2801,12 @@ public T accept(ParseTreeVisitor visitor) { public final IdentifierContext identifier() throws RecognitionException { IdentifierContext _localctx = new IdentifierContext(_ctx, getState()); - enterRule(_localctx, 56, RULE_identifier); + enterRule(_localctx, 52, RULE_identifier); int _la; try { enterOuterAlt(_localctx, 1); { - setState(380); + setState(360); _la = _input.LA(1); if ( !(_la==UNQUOTED_IDENTIFIER || _la==QUOTED_IDENTIFIER) ) { _errHandler.recoverInline(this); @@ -2996,11 +2854,11 @@ public T accept(ParseTreeVisitor visitor) { public final IdentifierPatternContext identifierPattern() throws RecognitionException { IdentifierPatternContext _localctx = new IdentifierPatternContext(_ctx, getState()); - enterRule(_localctx, 58, RULE_identifierPattern); + enterRule(_localctx, 54, RULE_identifierPattern); try { enterOuterAlt(_localctx, 1); { - setState(382); + setState(362); match(ID_PATTERN); } } @@ -3266,17 +3124,17 @@ public T accept(ParseTreeVisitor visitor) { public final ConstantContext constant() throws RecognitionException { ConstantContext _localctx = new ConstantContext(_ctx, getState()); - enterRule(_localctx, 60, RULE_constant); + enterRule(_localctx, 56, RULE_constant); int _la; try { - setState(426); + setState(406); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,39,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,37,_ctx) ) { case 1: _localctx = new NullLiteralContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(384); + setState(364); match(NULL); } break; @@ -3284,9 +3142,9 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new QualifiedIntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 2); { - setState(385); + setState(365); integerValue(); - setState(386); + setState(366); match(UNQUOTED_IDENTIFIER); } break; @@ -3294,7 +3152,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new DecimalLiteralContext(_localctx); enterOuterAlt(_localctx, 3); { - setState(388); + setState(368); decimalValue(); } break; @@ -3302,7 +3160,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new IntegerLiteralContext(_localctx); enterOuterAlt(_localctx, 4); { - setState(389); + setState(369); integerValue(); } break; @@ -3310,7 +3168,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanLiteralContext(_localctx); enterOuterAlt(_localctx, 5); { - setState(390); + setState(370); booleanValue(); } break; @@ -3318,7 +3176,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new InputParamContext(_localctx); enterOuterAlt(_localctx, 6); { - setState(391); + setState(371); match(PARAM); } break; @@ -3326,7 +3184,7 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringLiteralContext(_localctx); enterOuterAlt(_localctx, 7); { - setState(392); + setState(372); string(); } break; @@ -3334,27 +3192,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new NumericArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 8); { - setState(393); + setState(373); match(OPENING_BRACKET); - setState(394); + setState(374); numericValue(); - setState(399); + setState(379); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(395); + setState(375); match(COMMA); - setState(396); + setState(376); numericValue(); } } - setState(401); + setState(381); _errHandler.sync(this); _la = _input.LA(1); } - setState(402); + setState(382); match(CLOSING_BRACKET); } break; @@ -3362,27 +3220,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new BooleanArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 9); { - setState(404); + setState(384); match(OPENING_BRACKET); - setState(405); + setState(385); booleanValue(); - setState(410); + setState(390); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(406); + setState(386); match(COMMA); - setState(407); + setState(387); booleanValue(); } } - setState(412); + setState(392); _errHandler.sync(this); _la = _input.LA(1); } - setState(413); + setState(393); match(CLOSING_BRACKET); } break; @@ -3390,27 +3248,27 @@ public final ConstantContext constant() throws RecognitionException { _localctx = new StringArrayLiteralContext(_localctx); enterOuterAlt(_localctx, 10); { - setState(415); + setState(395); match(OPENING_BRACKET); - setState(416); + setState(396); string(); - setState(421); + setState(401); _errHandler.sync(this); _la = _input.LA(1); while (_la==COMMA) { { { - setState(417); + setState(397); match(COMMA); - setState(418); + setState(398); string(); } } - setState(423); + setState(403); _errHandler.sync(this); _la = _input.LA(1); } - setState(424); + setState(404); match(CLOSING_BRACKET); } break; @@ -3453,13 +3311,13 @@ public T accept(ParseTreeVisitor visitor) { public final LimitCommandContext limitCommand() throws RecognitionException { LimitCommandContext _localctx = new LimitCommandContext(_ctx, getState()); - enterRule(_localctx, 62, RULE_limitCommand); + enterRule(_localctx, 58, RULE_limitCommand); try { enterOuterAlt(_localctx, 1); { - setState(428); + setState(408); match(LIMIT); - setState(429); + setState(409); match(INTEGER_LITERAL); } } @@ -3509,32 +3367,32 @@ public T accept(ParseTreeVisitor visitor) { public final SortCommandContext sortCommand() throws RecognitionException { SortCommandContext _localctx = new SortCommandContext(_ctx, getState()); - enterRule(_localctx, 64, RULE_sortCommand); + enterRule(_localctx, 60, RULE_sortCommand); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(431); + setState(411); match(SORT); - setState(432); + setState(412); orderExpression(); - setState(437); + setState(417); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,40,_ctx); + _alt = getInterpreter().adaptivePredict(_input,38,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(433); + setState(413); match(COMMA); - setState(434); + setState(414); orderExpression(); } } } - setState(439); + setState(419); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,40,_ctx); + _alt = getInterpreter().adaptivePredict(_input,38,_ctx); } } } @@ -3583,19 +3441,19 @@ public T accept(ParseTreeVisitor visitor) { public final OrderExpressionContext orderExpression() throws RecognitionException { OrderExpressionContext _localctx = new OrderExpressionContext(_ctx, getState()); - enterRule(_localctx, 66, RULE_orderExpression); + enterRule(_localctx, 62, RULE_orderExpression); int _la; try { enterOuterAlt(_localctx, 1); { - setState(440); + setState(420); booleanExpression(0); - setState(442); + setState(422); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,41,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,39,_ctx) ) { case 1: { - setState(441); + setState(421); ((OrderExpressionContext)_localctx).ordering = _input.LT(1); _la = _input.LA(1); if ( !(_la==ASC || _la==DESC) ) { @@ -3609,14 +3467,14 @@ public final OrderExpressionContext orderExpression() throws RecognitionExceptio } break; } - setState(446); + setState(426); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,42,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,40,_ctx) ) { case 1: { - setState(444); + setState(424); match(NULLS); - setState(445); + setState(425); ((OrderExpressionContext)_localctx).nullOrdering = _input.LT(1); _la = _input.LA(1); if ( !(_la==FIRST || _la==LAST) ) { @@ -3678,32 +3536,32 @@ public T accept(ParseTreeVisitor visitor) { public final KeepCommandContext keepCommand() throws RecognitionException { KeepCommandContext _localctx = new KeepCommandContext(_ctx, getState()); - enterRule(_localctx, 68, RULE_keepCommand); + enterRule(_localctx, 64, RULE_keepCommand); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(448); + setState(428); match(KEEP); - setState(449); + setState(429); qualifiedNamePattern(); - setState(454); + setState(434); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,43,_ctx); + _alt = getInterpreter().adaptivePredict(_input,41,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(450); + setState(430); match(COMMA); - setState(451); + setState(431); qualifiedNamePattern(); } } } - setState(456); + setState(436); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,43,_ctx); + _alt = getInterpreter().adaptivePredict(_input,41,_ctx); } } } @@ -3753,32 +3611,32 @@ public T accept(ParseTreeVisitor visitor) { public final DropCommandContext dropCommand() throws RecognitionException { DropCommandContext _localctx = new DropCommandContext(_ctx, getState()); - enterRule(_localctx, 70, RULE_dropCommand); + enterRule(_localctx, 66, RULE_dropCommand); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(457); + setState(437); match(DROP); - setState(458); + setState(438); qualifiedNamePattern(); - setState(463); + setState(443); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,44,_ctx); + _alt = getInterpreter().adaptivePredict(_input,42,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(459); + setState(439); match(COMMA); - setState(460); + setState(440); qualifiedNamePattern(); } } } - setState(465); + setState(445); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,44,_ctx); + _alt = getInterpreter().adaptivePredict(_input,42,_ctx); } } } @@ -3828,32 +3686,32 @@ public T accept(ParseTreeVisitor visitor) { public final RenameCommandContext renameCommand() throws RecognitionException { RenameCommandContext _localctx = new RenameCommandContext(_ctx, getState()); - enterRule(_localctx, 72, RULE_renameCommand); + enterRule(_localctx, 68, RULE_renameCommand); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(466); + setState(446); match(RENAME); - setState(467); + setState(447); renameClause(); - setState(472); + setState(452); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,45,_ctx); + _alt = getInterpreter().adaptivePredict(_input,43,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(468); + setState(448); match(COMMA); - setState(469); + setState(449); renameClause(); } } } - setState(474); + setState(454); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,45,_ctx); + _alt = getInterpreter().adaptivePredict(_input,43,_ctx); } } } @@ -3901,15 +3759,15 @@ public T accept(ParseTreeVisitor visitor) { public final RenameClauseContext renameClause() throws RecognitionException { RenameClauseContext _localctx = new RenameClauseContext(_ctx, getState()); - enterRule(_localctx, 74, RULE_renameClause); + enterRule(_localctx, 70, RULE_renameClause); try { enterOuterAlt(_localctx, 1); { - setState(475); + setState(455); ((RenameClauseContext)_localctx).oldName = qualifiedNamePattern(); - setState(476); + setState(456); match(AS); - setState(477); + setState(457); ((RenameClauseContext)_localctx).newName = qualifiedNamePattern(); } } @@ -3958,22 +3816,22 @@ public T accept(ParseTreeVisitor visitor) { public final DissectCommandContext dissectCommand() throws RecognitionException { DissectCommandContext _localctx = new DissectCommandContext(_ctx, getState()); - enterRule(_localctx, 76, RULE_dissectCommand); + enterRule(_localctx, 72, RULE_dissectCommand); try { enterOuterAlt(_localctx, 1); { - setState(479); + setState(459); match(DISSECT); - setState(480); + setState(460); primaryExpression(0); - setState(481); + setState(461); string(); - setState(483); + setState(463); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,44,_ctx) ) { case 1: { - setState(482); + setState(462); commandOptions(); } break; @@ -4022,15 +3880,15 @@ public T accept(ParseTreeVisitor visitor) { public final GrokCommandContext grokCommand() throws RecognitionException { GrokCommandContext _localctx = new GrokCommandContext(_ctx, getState()); - enterRule(_localctx, 78, RULE_grokCommand); + enterRule(_localctx, 74, RULE_grokCommand); try { enterOuterAlt(_localctx, 1); { - setState(485); + setState(465); match(GROK); - setState(486); + setState(466); primaryExpression(0); - setState(487); + setState(467); string(); } } @@ -4073,13 +3931,13 @@ public T accept(ParseTreeVisitor visitor) { public final MvExpandCommandContext mvExpandCommand() throws RecognitionException { MvExpandCommandContext _localctx = new MvExpandCommandContext(_ctx, getState()); - enterRule(_localctx, 80, RULE_mvExpandCommand); + enterRule(_localctx, 76, RULE_mvExpandCommand); try { enterOuterAlt(_localctx, 1); { - setState(489); + setState(469); match(MV_EXPAND); - setState(490); + setState(470); qualifiedName(); } } @@ -4128,30 +3986,30 @@ public T accept(ParseTreeVisitor visitor) { public final CommandOptionsContext commandOptions() throws RecognitionException { CommandOptionsContext _localctx = new CommandOptionsContext(_ctx, getState()); - enterRule(_localctx, 82, RULE_commandOptions); + enterRule(_localctx, 78, RULE_commandOptions); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(492); + setState(472); commandOption(); - setState(497); + setState(477); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,47,_ctx); + _alt = getInterpreter().adaptivePredict(_input,45,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(493); + setState(473); match(COMMA); - setState(494); + setState(474); commandOption(); } } } - setState(499); + setState(479); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,47,_ctx); + _alt = getInterpreter().adaptivePredict(_input,45,_ctx); } } } @@ -4197,15 +4055,15 @@ public T accept(ParseTreeVisitor visitor) { public final CommandOptionContext commandOption() throws RecognitionException { CommandOptionContext _localctx = new CommandOptionContext(_ctx, getState()); - enterRule(_localctx, 84, RULE_commandOption); + enterRule(_localctx, 80, RULE_commandOption); try { enterOuterAlt(_localctx, 1); { - setState(500); + setState(480); identifier(); - setState(501); + setState(481); match(ASSIGN); - setState(502); + setState(482); constant(); } } @@ -4246,12 +4104,12 @@ public T accept(ParseTreeVisitor visitor) { public final BooleanValueContext booleanValue() throws RecognitionException { BooleanValueContext _localctx = new BooleanValueContext(_ctx, getState()); - enterRule(_localctx, 86, RULE_booleanValue); + enterRule(_localctx, 82, RULE_booleanValue); int _la; try { enterOuterAlt(_localctx, 1); { - setState(504); + setState(484); _la = _input.LA(1); if ( !(_la==FALSE || _la==TRUE) ) { _errHandler.recoverInline(this); @@ -4304,22 +4162,22 @@ public T accept(ParseTreeVisitor visitor) { public final NumericValueContext numericValue() throws RecognitionException { NumericValueContext _localctx = new NumericValueContext(_ctx, getState()); - enterRule(_localctx, 88, RULE_numericValue); + enterRule(_localctx, 84, RULE_numericValue); try { - setState(508); + setState(488); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,48,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(506); + setState(486); decimalValue(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(507); + setState(487); integerValue(); } break; @@ -4363,17 +4221,17 @@ public T accept(ParseTreeVisitor visitor) { public final DecimalValueContext decimalValue() throws RecognitionException { DecimalValueContext _localctx = new DecimalValueContext(_ctx, getState()); - enterRule(_localctx, 90, RULE_decimalValue); + enterRule(_localctx, 86, RULE_decimalValue); int _la; try { enterOuterAlt(_localctx, 1); { - setState(511); + setState(491); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(510); + setState(490); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -4386,7 +4244,7 @@ public final DecimalValueContext decimalValue() throws RecognitionException { } } - setState(513); + setState(493); match(DECIMAL_LITERAL); } } @@ -4428,17 +4286,17 @@ public T accept(ParseTreeVisitor visitor) { public final IntegerValueContext integerValue() throws RecognitionException { IntegerValueContext _localctx = new IntegerValueContext(_ctx, getState()); - enterRule(_localctx, 92, RULE_integerValue); + enterRule(_localctx, 88, RULE_integerValue); int _la; try { enterOuterAlt(_localctx, 1); { - setState(516); + setState(496); _errHandler.sync(this); _la = _input.LA(1); if (_la==PLUS || _la==MINUS) { { - setState(515); + setState(495); _la = _input.LA(1); if ( !(_la==PLUS || _la==MINUS) ) { _errHandler.recoverInline(this); @@ -4451,7 +4309,7 @@ public final IntegerValueContext integerValue() throws RecognitionException { } } - setState(518); + setState(498); match(INTEGER_LITERAL); } } @@ -4491,11 +4349,11 @@ public T accept(ParseTreeVisitor visitor) { public final StringContext string() throws RecognitionException { StringContext _localctx = new StringContext(_ctx, getState()); - enterRule(_localctx, 94, RULE_string); + enterRule(_localctx, 90, RULE_string); try { enterOuterAlt(_localctx, 1); { - setState(520); + setState(500); match(QUOTED_STRING); } } @@ -4540,12 +4398,12 @@ public T accept(ParseTreeVisitor visitor) { public final ComparisonOperatorContext comparisonOperator() throws RecognitionException { ComparisonOperatorContext _localctx = new ComparisonOperatorContext(_ctx, getState()); - enterRule(_localctx, 96, RULE_comparisonOperator); + enterRule(_localctx, 92, RULE_comparisonOperator); int _la; try { enterOuterAlt(_localctx, 1); { - setState(522); + setState(502); _la = _input.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 4503599627370496000L) != 0)) ) { _errHandler.recoverInline(this); @@ -4596,13 +4454,13 @@ public T accept(ParseTreeVisitor visitor) { public final ExplainCommandContext explainCommand() throws RecognitionException { ExplainCommandContext _localctx = new ExplainCommandContext(_ctx, getState()); - enterRule(_localctx, 98, RULE_explainCommand); + enterRule(_localctx, 94, RULE_explainCommand); try { enterOuterAlt(_localctx, 1); { - setState(524); + setState(504); match(EXPLAIN); - setState(525); + setState(505); subqueryExpression(); } } @@ -4646,15 +4504,15 @@ public T accept(ParseTreeVisitor visitor) { public final SubqueryExpressionContext subqueryExpression() throws RecognitionException { SubqueryExpressionContext _localctx = new SubqueryExpressionContext(_ctx, getState()); - enterRule(_localctx, 100, RULE_subqueryExpression); + enterRule(_localctx, 96, RULE_subqueryExpression); try { enterOuterAlt(_localctx, 1); { - setState(527); + setState(507); match(OPENING_BRACKET); - setState(528); + setState(508); query(0); - setState(529); + setState(509); match(CLOSING_BRACKET); } } @@ -4706,14 +4564,14 @@ public T accept(ParseTreeVisitor visitor) { public final ShowCommandContext showCommand() throws RecognitionException { ShowCommandContext _localctx = new ShowCommandContext(_ctx, getState()); - enterRule(_localctx, 102, RULE_showCommand); + enterRule(_localctx, 98, RULE_showCommand); try { _localctx = new ShowInfoContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(531); + setState(511); match(SHOW); - setState(532); + setState(512); match(INFO); } } @@ -4765,14 +4623,14 @@ public T accept(ParseTreeVisitor visitor) { public final MetaCommandContext metaCommand() throws RecognitionException { MetaCommandContext _localctx = new MetaCommandContext(_ctx, getState()); - enterRule(_localctx, 104, RULE_metaCommand); + enterRule(_localctx, 100, RULE_metaCommand); try { _localctx = new MetaFunctionsContext(_localctx); enterOuterAlt(_localctx, 1); { - setState(534); + setState(514); match(META); - setState(535); + setState(515); match(FUNCTIONS); } } @@ -4830,53 +4688,53 @@ public T accept(ParseTreeVisitor visitor) { public final EnrichCommandContext enrichCommand() throws RecognitionException { EnrichCommandContext _localctx = new EnrichCommandContext(_ctx, getState()); - enterRule(_localctx, 106, RULE_enrichCommand); + enterRule(_localctx, 102, RULE_enrichCommand); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(537); + setState(517); match(ENRICH); - setState(538); + setState(518); ((EnrichCommandContext)_localctx).policyName = match(ENRICH_POLICY_NAME); - setState(541); + setState(521); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,51,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,49,_ctx) ) { case 1: { - setState(539); + setState(519); match(ON); - setState(540); + setState(520); ((EnrichCommandContext)_localctx).matchField = qualifiedNamePattern(); } break; } - setState(552); + setState(532); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,53,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,51,_ctx) ) { case 1: { - setState(543); + setState(523); match(WITH); - setState(544); + setState(524); enrichWithClause(); - setState(549); + setState(529); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,52,_ctx); + _alt = getInterpreter().adaptivePredict(_input,50,_ctx); while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { { { - setState(545); + setState(525); match(COMMA); - setState(546); + setState(526); enrichWithClause(); } } } - setState(551); + setState(531); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,52,_ctx); + _alt = getInterpreter().adaptivePredict(_input,50,_ctx); } } break; @@ -4927,23 +4785,23 @@ public T accept(ParseTreeVisitor visitor) { public final EnrichWithClauseContext enrichWithClause() throws RecognitionException { EnrichWithClauseContext _localctx = new EnrichWithClauseContext(_ctx, getState()); - enterRule(_localctx, 108, RULE_enrichWithClause); + enterRule(_localctx, 104, RULE_enrichWithClause); try { enterOuterAlt(_localctx, 1); { - setState(557); + setState(537); _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,54,_ctx) ) { + switch ( getInterpreter().adaptivePredict(_input,52,_ctx) ) { case 1: { - setState(554); + setState(534); ((EnrichWithClauseContext)_localctx).newName = qualifiedNamePattern(); - setState(555); + setState(535); match(ASSIGN); } break; } - setState(559); + setState(539); ((EnrichWithClauseContext)_localctx).enrichField = qualifiedNamePattern(); } } @@ -5005,7 +4863,7 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in } public static final String _serializedATN = - "\u0004\u0001u\u0232\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ + "\u0004\u0001t\u021e\u0002\u0000\u0007\u0000\u0002\u0001\u0007\u0001\u0002"+ "\u0002\u0007\u0002\u0002\u0003\u0007\u0003\u0002\u0004\u0007\u0004\u0002"+ "\u0005\u0007\u0005\u0002\u0006\u0007\u0006\u0002\u0007\u0007\u0007\u0002"+ "\b\u0007\b\u0002\t\u0007\t\u0002\n\u0007\n\u0002\u000b\u0007\u000b\u0002"+ @@ -5019,350 +4877,338 @@ private boolean primaryExpression_sempred(PrimaryExpressionContext _localctx, in "#\u0007#\u0002$\u0007$\u0002%\u0007%\u0002&\u0007&\u0002\'\u0007\'\u0002"+ "(\u0007(\u0002)\u0007)\u0002*\u0007*\u0002+\u0007+\u0002,\u0007,\u0002"+ "-\u0007-\u0002.\u0007.\u0002/\u0007/\u00020\u00070\u00021\u00071\u0002"+ - "2\u00072\u00023\u00073\u00024\u00074\u00025\u00075\u00026\u00076\u0001"+ - "\u0000\u0001\u0000\u0001\u0000\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ - "\u0001\u0001\u0001\u0001\u0001\u0005\u0001x\b\u0001\n\u0001\f\u0001{\t"+ - "\u0001\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0001"+ - "\u0002\u0003\u0002\u0083\b\u0002\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+ - "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001"+ - "\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0003\u0003\u0092\b\u0003\u0001"+ - "\u0004\u0001\u0004\u0001\u0004\u0001\u0005\u0001\u0005\u0001\u0005\u0001"+ - "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003\u0005\u009e\b\u0005\u0001"+ - "\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0005\u0005\u00a5"+ - "\b\u0005\n\u0005\f\u0005\u00a8\t\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ - "\u0001\u0005\u0001\u0005\u0003\u0005\u00af\b\u0005\u0001\u0005\u0001\u0005"+ - "\u0003\u0005\u00b3\b\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ - "\u0001\u0005\u0001\u0005\u0005\u0005\u00bb\b\u0005\n\u0005\f\u0005\u00be"+ - "\t\u0005\u0001\u0006\u0001\u0006\u0003\u0006\u00c2\b\u0006\u0001\u0006"+ - "\u0001\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0003\u0006\u00c9\b\u0006"+ - "\u0001\u0006\u0001\u0006\u0001\u0006\u0003\u0006\u00ce\b\u0006\u0001\u0007"+ - "\u0001\u0007\u0001\u0007\u0001\u0007\u0001\u0007\u0003\u0007\u00d5\b\u0007"+ - "\u0001\b\u0001\b\u0001\b\u0001\b\u0003\b\u00db\b\b\u0001\b\u0001\b\u0001"+ - "\b\u0001\b\u0001\b\u0001\b\u0005\b\u00e3\b\b\n\b\f\b\u00e6\t\b\u0001\t"+ - "\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t\u0003\t\u00f0"+ - "\b\t\u0001\t\u0001\t\u0001\t\u0005\t\u00f5\b\t\n\t\f\t\u00f8\t\t\u0001"+ - "\n\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n\u0005\n\u0100\b\n\n\n\f\n\u0103"+ - "\t\n\u0003\n\u0105\b\n\u0001\n\u0001\n\u0001\u000b\u0001\u000b\u0001\f"+ - "\u0001\f\u0001\f\u0001\r\u0001\r\u0001\r\u0005\r\u0111\b\r\n\r\f\r\u0114"+ - "\t\r\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0001\u000e\u0003"+ - "\u000e\u011b\b\u000e\u0001\u000f\u0001\u000f\u0001\u000f\u0001\u000f\u0005"+ - "\u000f\u0121\b\u000f\n\u000f\f\u000f\u0124\t\u000f\u0001\u000f\u0003\u000f"+ - "\u0127\b\u000f\u0001\u000f\u0003\u000f\u012a\b\u000f\u0001\u0010\u0001"+ - "\u0010\u0001\u0011\u0001\u0011\u0001\u0011\u0001\u0011\u0005\u0011\u0132"+ - "\b\u0011\n\u0011\f\u0011\u0135\t\u0011\u0001\u0012\u0001\u0012\u0001\u0012"+ - "\u0001\u0012\u0001\u0013\u0001\u0013\u0003\u0013\u013d\b\u0013\u0001\u0014"+ - "\u0001\u0014\u0001\u0014\u0001\u0014\u0005\u0014\u0143\b\u0014\n\u0014"+ - "\f\u0014\u0146\t\u0014\u0001\u0015\u0001\u0015\u0001\u0015\u0001\u0015"+ - "\u0001\u0016\u0001\u0016\u0001\u0016\u0001\u0016\u0005\u0016\u0150\b\u0016"+ - "\n\u0016\f\u0016\u0153\t\u0016\u0001\u0016\u0003\u0016\u0156\b\u0016\u0001"+ - "\u0016\u0001\u0016\u0003\u0016\u015a\b\u0016\u0001\u0017\u0001\u0017\u0001"+ - "\u0017\u0001\u0018\u0001\u0018\u0003\u0018\u0161\b\u0018\u0001\u0018\u0001"+ - "\u0018\u0003\u0018\u0165\b\u0018\u0001\u0019\u0001\u0019\u0001\u0019\u0001"+ - "\u0019\u0003\u0019\u016b\b\u0019\u0001\u001a\u0001\u001a\u0001\u001a\u0005"+ - "\u001a\u0170\b\u001a\n\u001a\f\u001a\u0173\t\u001a\u0001\u001b\u0001\u001b"+ - "\u0001\u001b\u0005\u001b\u0178\b\u001b\n\u001b\f\u001b\u017b\t\u001b\u0001"+ - "\u001c\u0001\u001c\u0001\u001d\u0001\u001d\u0001\u001e\u0001\u001e\u0001"+ - "\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001"+ - "\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0005\u001e\u018e"+ - "\b\u001e\n\u001e\f\u001e\u0191\t\u001e\u0001\u001e\u0001\u001e\u0001\u001e"+ - "\u0001\u001e\u0001\u001e\u0001\u001e\u0005\u001e\u0199\b\u001e\n\u001e"+ - "\f\u001e\u019c\t\u001e\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e"+ - "\u0001\u001e\u0001\u001e\u0005\u001e\u01a4\b\u001e\n\u001e\f\u001e\u01a7"+ - "\t\u001e\u0001\u001e\u0001\u001e\u0003\u001e\u01ab\b\u001e\u0001\u001f"+ - "\u0001\u001f\u0001\u001f\u0001 \u0001 \u0001 \u0001 \u0005 \u01b4\b \n"+ - " \f \u01b7\t \u0001!\u0001!\u0003!\u01bb\b!\u0001!\u0001!\u0003!\u01bf"+ - "\b!\u0001\"\u0001\"\u0001\"\u0001\"\u0005\"\u01c5\b\"\n\"\f\"\u01c8\t"+ - "\"\u0001#\u0001#\u0001#\u0001#\u0005#\u01ce\b#\n#\f#\u01d1\t#\u0001$\u0001"+ - "$\u0001$\u0001$\u0005$\u01d7\b$\n$\f$\u01da\t$\u0001%\u0001%\u0001%\u0001"+ - "%\u0001&\u0001&\u0001&\u0001&\u0003&\u01e4\b&\u0001\'\u0001\'\u0001\'"+ - "\u0001\'\u0001(\u0001(\u0001(\u0001)\u0001)\u0001)\u0005)\u01f0\b)\n)"+ - "\f)\u01f3\t)\u0001*\u0001*\u0001*\u0001*\u0001+\u0001+\u0001,\u0001,\u0003"+ - ",\u01fd\b,\u0001-\u0003-\u0200\b-\u0001-\u0001-\u0001.\u0003.\u0205\b"+ - ".\u0001.\u0001.\u0001/\u0001/\u00010\u00010\u00011\u00011\u00011\u0001"+ - "2\u00012\u00012\u00012\u00013\u00013\u00013\u00014\u00014\u00014\u0001"+ - "5\u00015\u00015\u00015\u00035\u021e\b5\u00015\u00015\u00015\u00015\u0005"+ - "5\u0224\b5\n5\f5\u0227\t5\u00035\u0229\b5\u00016\u00016\u00016\u00036"+ - "\u022e\b6\u00016\u00016\u00016\u0000\u0004\u0002\n\u0010\u00127\u0000"+ - "\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a\u001c"+ - "\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfhjl\u0000\u0007\u0001\u0000"+ - ">?\u0001\u0000@B\u0001\u0000EF\u0002\u0000\"\"&&\u0001\u0000)*\u0002\u0000"+ - "((66\u0002\u0000779=\u024e\u0000n\u0001\u0000\u0000\u0000\u0002q\u0001"+ - "\u0000\u0000\u0000\u0004\u0082\u0001\u0000\u0000\u0000\u0006\u0091\u0001"+ - "\u0000\u0000\u0000\b\u0093\u0001\u0000\u0000\u0000\n\u00b2\u0001\u0000"+ - "\u0000\u0000\f\u00cd\u0001\u0000\u0000\u0000\u000e\u00d4\u0001\u0000\u0000"+ - "\u0000\u0010\u00da\u0001\u0000\u0000\u0000\u0012\u00ef\u0001\u0000\u0000"+ - "\u0000\u0014\u00f9\u0001\u0000\u0000\u0000\u0016\u0108\u0001\u0000\u0000"+ - "\u0000\u0018\u010a\u0001\u0000\u0000\u0000\u001a\u010d\u0001\u0000\u0000"+ - "\u0000\u001c\u011a\u0001\u0000\u0000\u0000\u001e\u011c\u0001\u0000\u0000"+ - "\u0000 \u012b\u0001\u0000\u0000\u0000\"\u012d\u0001\u0000\u0000\u0000"+ - "$\u0136\u0001\u0000\u0000\u0000&\u013c\u0001\u0000\u0000\u0000(\u013e"+ - "\u0001\u0000\u0000\u0000*\u0147\u0001\u0000\u0000\u0000,\u014b\u0001\u0000"+ - "\u0000\u0000.\u015b\u0001\u0000\u0000\u00000\u015e\u0001\u0000\u0000\u0000"+ - "2\u0166\u0001\u0000\u0000\u00004\u016c\u0001\u0000\u0000\u00006\u0174"+ - "\u0001\u0000\u0000\u00008\u017c\u0001\u0000\u0000\u0000:\u017e\u0001\u0000"+ - "\u0000\u0000<\u01aa\u0001\u0000\u0000\u0000>\u01ac\u0001\u0000\u0000\u0000"+ - "@\u01af\u0001\u0000\u0000\u0000B\u01b8\u0001\u0000\u0000\u0000D\u01c0"+ - "\u0001\u0000\u0000\u0000F\u01c9\u0001\u0000\u0000\u0000H\u01d2\u0001\u0000"+ - "\u0000\u0000J\u01db\u0001\u0000\u0000\u0000L\u01df\u0001\u0000\u0000\u0000"+ - "N\u01e5\u0001\u0000\u0000\u0000P\u01e9\u0001\u0000\u0000\u0000R\u01ec"+ - "\u0001\u0000\u0000\u0000T\u01f4\u0001\u0000\u0000\u0000V\u01f8\u0001\u0000"+ - "\u0000\u0000X\u01fc\u0001\u0000\u0000\u0000Z\u01ff\u0001\u0000\u0000\u0000"+ - "\\\u0204\u0001\u0000\u0000\u0000^\u0208\u0001\u0000\u0000\u0000`\u020a"+ - "\u0001\u0000\u0000\u0000b\u020c\u0001\u0000\u0000\u0000d\u020f\u0001\u0000"+ - "\u0000\u0000f\u0213\u0001\u0000\u0000\u0000h\u0216\u0001\u0000\u0000\u0000"+ - "j\u0219\u0001\u0000\u0000\u0000l\u022d\u0001\u0000\u0000\u0000no\u0003"+ - "\u0002\u0001\u0000op\u0005\u0000\u0000\u0001p\u0001\u0001\u0000\u0000"+ - "\u0000qr\u0006\u0001\uffff\uffff\u0000rs\u0003\u0004\u0002\u0000sy\u0001"+ - "\u0000\u0000\u0000tu\n\u0001\u0000\u0000uv\u0005\u001c\u0000\u0000vx\u0003"+ - "\u0006\u0003\u0000wt\u0001\u0000\u0000\u0000x{\u0001\u0000\u0000\u0000"+ - "yw\u0001\u0000\u0000\u0000yz\u0001\u0000\u0000\u0000z\u0003\u0001\u0000"+ - "\u0000\u0000{y\u0001\u0000\u0000\u0000|\u0083\u0003b1\u0000}\u0083\u0003"+ - "\u001e\u000f\u0000~\u0083\u0003\u0018\f\u0000\u007f\u0083\u0003,\u0016"+ - "\u0000\u0080\u0083\u0003f3\u0000\u0081\u0083\u0003h4\u0000\u0082|\u0001"+ - "\u0000\u0000\u0000\u0082}\u0001\u0000\u0000\u0000\u0082~\u0001\u0000\u0000"+ - "\u0000\u0082\u007f\u0001\u0000\u0000\u0000\u0082\u0080\u0001\u0000\u0000"+ - "\u0000\u0082\u0081\u0001\u0000\u0000\u0000\u0083\u0005\u0001\u0000\u0000"+ - "\u0000\u0084\u0092\u0003.\u0017\u0000\u0085\u0092\u00032\u0019\u0000\u0086"+ - "\u0092\u0003>\u001f\u0000\u0087\u0092\u0003D\"\u0000\u0088\u0092\u0003"+ - "@ \u0000\u0089\u0092\u00030\u0018\u0000\u008a\u0092\u0003\b\u0004\u0000"+ - "\u008b\u0092\u0003F#\u0000\u008c\u0092\u0003H$\u0000\u008d\u0092\u0003"+ - "L&\u0000\u008e\u0092\u0003N\'\u0000\u008f\u0092\u0003j5\u0000\u0090\u0092"+ - "\u0003P(\u0000\u0091\u0084\u0001\u0000\u0000\u0000\u0091\u0085\u0001\u0000"+ - "\u0000\u0000\u0091\u0086\u0001\u0000\u0000\u0000\u0091\u0087\u0001\u0000"+ - "\u0000\u0000\u0091\u0088\u0001\u0000\u0000\u0000\u0091\u0089\u0001\u0000"+ - "\u0000\u0000\u0091\u008a\u0001\u0000\u0000\u0000\u0091\u008b\u0001\u0000"+ - "\u0000\u0000\u0091\u008c\u0001\u0000\u0000\u0000\u0091\u008d\u0001\u0000"+ - "\u0000\u0000\u0091\u008e\u0001\u0000\u0000\u0000\u0091\u008f\u0001\u0000"+ - "\u0000\u0000\u0091\u0090\u0001\u0000\u0000\u0000\u0092\u0007\u0001\u0000"+ - "\u0000\u0000\u0093\u0094\u0005\u0013\u0000\u0000\u0094\u0095\u0003\n\u0005"+ - "\u0000\u0095\t\u0001\u0000\u0000\u0000\u0096\u0097\u0006\u0005\uffff\uffff"+ - "\u0000\u0097\u0098\u0005/\u0000\u0000\u0098\u00b3\u0003\n\u0005\u0007"+ - "\u0099\u00b3\u0003\u000e\u0007\u0000\u009a\u00b3\u0003\f\u0006\u0000\u009b"+ - "\u009d\u0003\u000e\u0007\u0000\u009c\u009e\u0005/\u0000\u0000\u009d\u009c"+ - "\u0001\u0000\u0000\u0000\u009d\u009e\u0001\u0000\u0000\u0000\u009e\u009f"+ - "\u0001\u0000\u0000\u0000\u009f\u00a0\u0005,\u0000\u0000\u00a0\u00a1\u0005"+ - "+\u0000\u0000\u00a1\u00a6\u0003\u000e\u0007\u0000\u00a2\u00a3\u0005%\u0000"+ - "\u0000\u00a3\u00a5\u0003\u000e\u0007\u0000\u00a4\u00a2\u0001\u0000\u0000"+ - "\u0000\u00a5\u00a8\u0001\u0000\u0000\u0000\u00a6\u00a4\u0001\u0000\u0000"+ - "\u0000\u00a6\u00a7\u0001\u0000\u0000\u0000\u00a7\u00a9\u0001\u0000\u0000"+ - "\u0000\u00a8\u00a6\u0001\u0000\u0000\u0000\u00a9\u00aa\u00055\u0000\u0000"+ - "\u00aa\u00b3\u0001\u0000\u0000\u0000\u00ab\u00ac\u0003\u000e\u0007\u0000"+ - "\u00ac\u00ae\u0005-\u0000\u0000\u00ad\u00af\u0005/\u0000\u0000\u00ae\u00ad"+ - "\u0001\u0000\u0000\u0000\u00ae\u00af\u0001\u0000\u0000\u0000\u00af\u00b0"+ - "\u0001\u0000\u0000\u0000\u00b0\u00b1\u00050\u0000\u0000\u00b1\u00b3\u0001"+ - "\u0000\u0000\u0000\u00b2\u0096\u0001\u0000\u0000\u0000\u00b2\u0099\u0001"+ - "\u0000\u0000\u0000\u00b2\u009a\u0001\u0000\u0000\u0000\u00b2\u009b\u0001"+ - "\u0000\u0000\u0000\u00b2\u00ab\u0001\u0000\u0000\u0000\u00b3\u00bc\u0001"+ - "\u0000\u0000\u0000\u00b4\u00b5\n\u0004\u0000\u0000\u00b5\u00b6\u0005!"+ - "\u0000\u0000\u00b6\u00bb\u0003\n\u0005\u0005\u00b7\u00b8\n\u0003\u0000"+ - "\u0000\u00b8\u00b9\u00052\u0000\u0000\u00b9\u00bb\u0003\n\u0005\u0004"+ - "\u00ba\u00b4\u0001\u0000\u0000\u0000\u00ba\u00b7\u0001\u0000\u0000\u0000"+ - "\u00bb\u00be\u0001\u0000\u0000\u0000\u00bc\u00ba\u0001\u0000\u0000\u0000"+ - "\u00bc\u00bd\u0001\u0000\u0000\u0000\u00bd\u000b\u0001\u0000\u0000\u0000"+ - "\u00be\u00bc\u0001\u0000\u0000\u0000\u00bf\u00c1\u0003\u000e\u0007\u0000"+ - "\u00c0\u00c2\u0005/\u0000\u0000\u00c1\u00c0\u0001\u0000\u0000\u0000\u00c1"+ - "\u00c2\u0001\u0000\u0000\u0000\u00c2\u00c3\u0001\u0000\u0000\u0000\u00c3"+ - "\u00c4\u0005.\u0000\u0000\u00c4\u00c5\u0003^/\u0000\u00c5\u00ce\u0001"+ - "\u0000\u0000\u0000\u00c6\u00c8\u0003\u000e\u0007\u0000\u00c7\u00c9\u0005"+ - "/\u0000\u0000\u00c8\u00c7\u0001\u0000\u0000\u0000\u00c8\u00c9\u0001\u0000"+ - "\u0000\u0000\u00c9\u00ca\u0001\u0000\u0000\u0000\u00ca\u00cb\u00054\u0000"+ - "\u0000\u00cb\u00cc\u0003^/\u0000\u00cc\u00ce\u0001\u0000\u0000\u0000\u00cd"+ - "\u00bf\u0001\u0000\u0000\u0000\u00cd\u00c6\u0001\u0000\u0000\u0000\u00ce"+ - "\r\u0001\u0000\u0000\u0000\u00cf\u00d5\u0003\u0010\b\u0000\u00d0\u00d1"+ - "\u0003\u0010\b\u0000\u00d1\u00d2\u0003`0\u0000\u00d2\u00d3\u0003\u0010"+ - "\b\u0000\u00d3\u00d5\u0001\u0000\u0000\u0000\u00d4\u00cf\u0001\u0000\u0000"+ - "\u0000\u00d4\u00d0\u0001\u0000\u0000\u0000\u00d5\u000f\u0001\u0000\u0000"+ - "\u0000\u00d6\u00d7\u0006\b\uffff\uffff\u0000\u00d7\u00db\u0003\u0012\t"+ - "\u0000\u00d8\u00d9\u0007\u0000\u0000\u0000\u00d9\u00db\u0003\u0010\b\u0003"+ - "\u00da\u00d6\u0001\u0000\u0000\u0000\u00da\u00d8\u0001\u0000\u0000\u0000"+ - "\u00db\u00e4\u0001\u0000\u0000\u0000\u00dc\u00dd\n\u0002\u0000\u0000\u00dd"+ - "\u00de\u0007\u0001\u0000\u0000\u00de\u00e3\u0003\u0010\b\u0003\u00df\u00e0"+ - "\n\u0001\u0000\u0000\u00e0\u00e1\u0007\u0000\u0000\u0000\u00e1\u00e3\u0003"+ - "\u0010\b\u0002\u00e2\u00dc\u0001\u0000\u0000\u0000\u00e2\u00df\u0001\u0000"+ - "\u0000\u0000\u00e3\u00e6\u0001\u0000\u0000\u0000\u00e4\u00e2\u0001\u0000"+ - "\u0000\u0000\u00e4\u00e5\u0001\u0000\u0000\u0000\u00e5\u0011\u0001\u0000"+ - "\u0000\u0000\u00e6\u00e4\u0001\u0000\u0000\u0000\u00e7\u00e8\u0006\t\uffff"+ - "\uffff\u0000\u00e8\u00f0\u0003<\u001e\u0000\u00e9\u00f0\u00034\u001a\u0000"+ - "\u00ea\u00f0\u0003\u0014\n\u0000\u00eb\u00ec\u0005+\u0000\u0000\u00ec"+ - "\u00ed\u0003\n\u0005\u0000\u00ed\u00ee\u00055\u0000\u0000\u00ee\u00f0"+ - "\u0001\u0000\u0000\u0000\u00ef\u00e7\u0001\u0000\u0000\u0000\u00ef\u00e9"+ - "\u0001\u0000\u0000\u0000\u00ef\u00ea\u0001\u0000\u0000\u0000\u00ef\u00eb"+ - "\u0001\u0000\u0000\u0000\u00f0\u00f6\u0001\u0000\u0000\u0000\u00f1\u00f2"+ - "\n\u0001\u0000\u0000\u00f2\u00f3\u0005$\u0000\u0000\u00f3\u00f5\u0003"+ - "\u0016\u000b\u0000\u00f4\u00f1\u0001\u0000\u0000\u0000\u00f5\u00f8\u0001"+ - "\u0000\u0000\u0000\u00f6\u00f4\u0001\u0000\u0000\u0000\u00f6\u00f7\u0001"+ - "\u0000\u0000\u0000\u00f7\u0013\u0001\u0000\u0000\u0000\u00f8\u00f6\u0001"+ - "\u0000\u0000\u0000\u00f9\u00fa\u00038\u001c\u0000\u00fa\u0104\u0005+\u0000"+ - "\u0000\u00fb\u0105\u0005@\u0000\u0000\u00fc\u0101\u0003\n\u0005\u0000"+ - "\u00fd\u00fe\u0005%\u0000\u0000\u00fe\u0100\u0003\n\u0005\u0000\u00ff"+ - "\u00fd\u0001\u0000\u0000\u0000\u0100\u0103\u0001\u0000\u0000\u0000\u0101"+ - "\u00ff\u0001\u0000\u0000\u0000\u0101\u0102\u0001\u0000\u0000\u0000\u0102"+ - "\u0105\u0001\u0000\u0000\u0000\u0103\u0101\u0001\u0000\u0000\u0000\u0104"+ - "\u00fb\u0001\u0000\u0000\u0000\u0104\u00fc\u0001\u0000\u0000\u0000\u0104"+ - "\u0105\u0001\u0000\u0000\u0000\u0105\u0106\u0001\u0000\u0000\u0000\u0106"+ - "\u0107\u00055\u0000\u0000\u0107\u0015\u0001\u0000\u0000\u0000\u0108\u0109"+ - "\u00038\u001c\u0000\u0109\u0017\u0001\u0000\u0000\u0000\u010a\u010b\u0005"+ - "\u000f\u0000\u0000\u010b\u010c\u0003\u001a\r\u0000\u010c\u0019\u0001\u0000"+ - "\u0000\u0000\u010d\u0112\u0003\u001c\u000e\u0000\u010e\u010f\u0005%\u0000"+ - "\u0000\u010f\u0111\u0003\u001c\u000e\u0000\u0110\u010e\u0001\u0000\u0000"+ - "\u0000\u0111\u0114\u0001\u0000\u0000\u0000\u0112\u0110\u0001\u0000\u0000"+ - "\u0000\u0112\u0113\u0001\u0000\u0000\u0000\u0113\u001b\u0001\u0000\u0000"+ - "\u0000\u0114\u0112\u0001\u0000\u0000\u0000\u0115\u011b\u0003\n\u0005\u0000"+ - "\u0116\u0117\u00034\u001a\u0000\u0117\u0118\u0005#\u0000\u0000\u0118\u0119"+ - "\u0003\n\u0005\u0000\u0119\u011b\u0001\u0000\u0000\u0000\u011a\u0115\u0001"+ - "\u0000\u0000\u0000\u011a\u0116\u0001\u0000\u0000\u0000\u011b\u001d\u0001"+ - "\u0000\u0000\u0000\u011c\u011d\u0005\u0006\u0000\u0000\u011d\u0122\u0003"+ - " \u0010\u0000\u011e\u011f\u0005%\u0000\u0000\u011f\u0121\u0003 \u0010"+ - "\u0000\u0120\u011e\u0001\u0000\u0000\u0000\u0121\u0124\u0001\u0000\u0000"+ - "\u0000\u0122\u0120\u0001\u0000\u0000\u0000\u0122\u0123\u0001\u0000\u0000"+ - "\u0000\u0123\u0126\u0001\u0000\u0000\u0000\u0124\u0122\u0001\u0000\u0000"+ - "\u0000\u0125\u0127\u0003&\u0013\u0000\u0126\u0125\u0001\u0000\u0000\u0000"+ - "\u0126\u0127\u0001\u0000\u0000\u0000\u0127\u0129\u0001\u0000\u0000\u0000"+ - "\u0128\u012a\u0003\"\u0011\u0000\u0129\u0128\u0001\u0000\u0000\u0000\u0129"+ - "\u012a\u0001\u0000\u0000\u0000\u012a\u001f\u0001\u0000\u0000\u0000\u012b"+ - "\u012c\u0005\u0018\u0000\u0000\u012c!\u0001\u0000\u0000\u0000\u012d\u012e"+ - "\u0005J\u0000\u0000\u012e\u0133\u0003$\u0012\u0000\u012f\u0130\u0005%"+ - "\u0000\u0000\u0130\u0132\u0003$\u0012\u0000\u0131\u012f\u0001\u0000\u0000"+ - "\u0000\u0132\u0135\u0001\u0000\u0000\u0000\u0133\u0131\u0001\u0000\u0000"+ - "\u0000\u0133\u0134\u0001\u0000\u0000\u0000\u0134#\u0001\u0000\u0000\u0000"+ - "\u0135\u0133\u0001\u0000\u0000\u0000\u0136\u0137\u0003^/\u0000\u0137\u0138"+ - "\u0005#\u0000\u0000\u0138\u0139\u0003^/\u0000\u0139%\u0001\u0000\u0000"+ - "\u0000\u013a\u013d\u0003(\u0014\u0000\u013b\u013d\u0003*\u0015\u0000\u013c"+ - "\u013a\u0001\u0000\u0000\u0000\u013c\u013b\u0001\u0000\u0000\u0000\u013d"+ - "\'\u0001\u0000\u0000\u0000\u013e\u013f\u0005K\u0000\u0000\u013f\u0144"+ - "\u0003 \u0010\u0000\u0140\u0141\u0005%\u0000\u0000\u0141\u0143\u0003 "+ - "\u0010\u0000\u0142\u0140\u0001\u0000\u0000\u0000\u0143\u0146\u0001\u0000"+ - "\u0000\u0000\u0144\u0142\u0001\u0000\u0000\u0000\u0144\u0145\u0001\u0000"+ - "\u0000\u0000\u0145)\u0001\u0000\u0000\u0000\u0146\u0144\u0001\u0000\u0000"+ - "\u0000\u0147\u0148\u0005C\u0000\u0000\u0148\u0149\u0003(\u0014\u0000\u0149"+ - "\u014a\u0005D\u0000\u0000\u014a+\u0001\u0000\u0000\u0000\u014b\u014c\u0005"+ - "\f\u0000\u0000\u014c\u0151\u0003 \u0010\u0000\u014d\u014e\u0005%\u0000"+ - "\u0000\u014e\u0150\u0003 \u0010\u0000\u014f\u014d\u0001\u0000\u0000\u0000"+ - "\u0150\u0153\u0001\u0000\u0000\u0000\u0151\u014f\u0001\u0000\u0000\u0000"+ - "\u0151\u0152\u0001\u0000\u0000\u0000\u0152\u0155\u0001\u0000\u0000\u0000"+ - "\u0153\u0151\u0001\u0000\u0000\u0000\u0154\u0156\u0003\u001a\r\u0000\u0155"+ - "\u0154\u0001\u0000\u0000\u0000\u0155\u0156\u0001\u0000\u0000\u0000\u0156"+ - "\u0159\u0001\u0000\u0000\u0000\u0157\u0158\u0005 \u0000\u0000\u0158\u015a"+ - "\u0003\u001a\r\u0000\u0159\u0157\u0001\u0000\u0000\u0000\u0159\u015a\u0001"+ - "\u0000\u0000\u0000\u015a-\u0001\u0000\u0000\u0000\u015b\u015c\u0005\u0004"+ - "\u0000\u0000\u015c\u015d\u0003\u001a\r\u0000\u015d/\u0001\u0000\u0000"+ - "\u0000\u015e\u0160\u0005\u0012\u0000\u0000\u015f\u0161\u0003\u001a\r\u0000"+ - "\u0160\u015f\u0001\u0000\u0000\u0000\u0160\u0161\u0001\u0000\u0000\u0000"+ - "\u0161\u0164\u0001\u0000\u0000\u0000\u0162\u0163\u0005 \u0000\u0000\u0163"+ - "\u0165\u0003\u001a\r\u0000\u0164\u0162\u0001\u0000\u0000\u0000\u0164\u0165"+ - "\u0001\u0000\u0000\u0000\u01651\u0001\u0000\u0000\u0000\u0166\u0167\u0005"+ - "\b\u0000\u0000\u0167\u016a\u0003\u001a\r\u0000\u0168\u0169\u0005 \u0000"+ - "\u0000\u0169\u016b\u0003\u001a\r\u0000\u016a\u0168\u0001\u0000\u0000\u0000"+ - "\u016a\u016b\u0001\u0000\u0000\u0000\u016b3\u0001\u0000\u0000\u0000\u016c"+ - "\u0171\u00038\u001c\u0000\u016d\u016e\u0005\'\u0000\u0000\u016e\u0170"+ - "\u00038\u001c\u0000\u016f\u016d\u0001\u0000\u0000\u0000\u0170\u0173\u0001"+ - "\u0000\u0000\u0000\u0171\u016f\u0001\u0000\u0000\u0000\u0171\u0172\u0001"+ - "\u0000\u0000\u0000\u01725\u0001\u0000\u0000\u0000\u0173\u0171\u0001\u0000"+ - "\u0000\u0000\u0174\u0179\u0003:\u001d\u0000\u0175\u0176\u0005\'\u0000"+ - "\u0000\u0176\u0178\u0003:\u001d\u0000\u0177\u0175\u0001\u0000\u0000\u0000"+ - "\u0178\u017b\u0001\u0000\u0000\u0000\u0179\u0177\u0001\u0000\u0000\u0000"+ - "\u0179\u017a\u0001\u0000\u0000\u0000\u017a7\u0001\u0000\u0000\u0000\u017b"+ - "\u0179\u0001\u0000\u0000\u0000\u017c\u017d\u0007\u0002\u0000\u0000\u017d"+ - "9\u0001\u0000\u0000\u0000\u017e\u017f\u0005O\u0000\u0000\u017f;\u0001"+ - "\u0000\u0000\u0000\u0180\u01ab\u00050\u0000\u0000\u0181\u0182\u0003\\"+ - ".\u0000\u0182\u0183\u0005E\u0000\u0000\u0183\u01ab\u0001\u0000\u0000\u0000"+ - "\u0184\u01ab\u0003Z-\u0000\u0185\u01ab\u0003\\.\u0000\u0186\u01ab\u0003"+ - "V+\u0000\u0187\u01ab\u00053\u0000\u0000\u0188\u01ab\u0003^/\u0000\u0189"+ - "\u018a\u0005C\u0000\u0000\u018a\u018f\u0003X,\u0000\u018b\u018c\u0005"+ - "%\u0000\u0000\u018c\u018e\u0003X,\u0000\u018d\u018b\u0001\u0000\u0000"+ - "\u0000\u018e\u0191\u0001\u0000\u0000\u0000\u018f\u018d\u0001\u0000\u0000"+ - "\u0000\u018f\u0190\u0001\u0000\u0000\u0000\u0190\u0192\u0001\u0000\u0000"+ - "\u0000\u0191\u018f\u0001\u0000\u0000\u0000\u0192\u0193\u0005D\u0000\u0000"+ - "\u0193\u01ab\u0001\u0000\u0000\u0000\u0194\u0195\u0005C\u0000\u0000\u0195"+ - "\u019a\u0003V+\u0000\u0196\u0197\u0005%\u0000\u0000\u0197\u0199\u0003"+ - "V+\u0000\u0198\u0196\u0001\u0000\u0000\u0000\u0199\u019c\u0001\u0000\u0000"+ - "\u0000\u019a\u0198\u0001\u0000\u0000\u0000\u019a\u019b\u0001\u0000\u0000"+ - "\u0000\u019b\u019d\u0001\u0000\u0000\u0000\u019c\u019a\u0001\u0000\u0000"+ - "\u0000\u019d\u019e\u0005D\u0000\u0000\u019e\u01ab\u0001\u0000\u0000\u0000"+ - "\u019f\u01a0\u0005C\u0000\u0000\u01a0\u01a5\u0003^/\u0000\u01a1\u01a2"+ - "\u0005%\u0000\u0000\u01a2\u01a4\u0003^/\u0000\u01a3\u01a1\u0001\u0000"+ - "\u0000\u0000\u01a4\u01a7\u0001\u0000\u0000\u0000\u01a5\u01a3\u0001\u0000"+ - "\u0000\u0000\u01a5\u01a6\u0001\u0000\u0000\u0000\u01a6\u01a8\u0001\u0000"+ - "\u0000\u0000\u01a7\u01a5\u0001\u0000\u0000\u0000\u01a8\u01a9\u0005D\u0000"+ - "\u0000\u01a9\u01ab\u0001\u0000\u0000\u0000\u01aa\u0180\u0001\u0000\u0000"+ - "\u0000\u01aa\u0181\u0001\u0000\u0000\u0000\u01aa\u0184\u0001\u0000\u0000"+ - "\u0000\u01aa\u0185\u0001\u0000\u0000\u0000\u01aa\u0186\u0001\u0000\u0000"+ - "\u0000\u01aa\u0187\u0001\u0000\u0000\u0000\u01aa\u0188\u0001\u0000\u0000"+ - "\u0000\u01aa\u0189\u0001\u0000\u0000\u0000\u01aa\u0194\u0001\u0000\u0000"+ - "\u0000\u01aa\u019f\u0001\u0000\u0000\u0000\u01ab=\u0001\u0000\u0000\u0000"+ - "\u01ac\u01ad\u0005\n\u0000\u0000\u01ad\u01ae\u0005\u001e\u0000\u0000\u01ae"+ - "?\u0001\u0000\u0000\u0000\u01af\u01b0\u0005\u0011\u0000\u0000\u01b0\u01b5"+ - "\u0003B!\u0000\u01b1\u01b2\u0005%\u0000\u0000\u01b2\u01b4\u0003B!\u0000"+ - "\u01b3\u01b1\u0001\u0000\u0000\u0000\u01b4\u01b7\u0001\u0000\u0000\u0000"+ - "\u01b5\u01b3\u0001\u0000\u0000\u0000\u01b5\u01b6\u0001\u0000\u0000\u0000"+ - "\u01b6A\u0001\u0000\u0000\u0000\u01b7\u01b5\u0001\u0000\u0000\u0000\u01b8"+ - "\u01ba\u0003\n\u0005\u0000\u01b9\u01bb\u0007\u0003\u0000\u0000\u01ba\u01b9"+ - "\u0001\u0000\u0000\u0000\u01ba\u01bb\u0001\u0000\u0000\u0000\u01bb\u01be"+ - "\u0001\u0000\u0000\u0000\u01bc\u01bd\u00051\u0000\u0000\u01bd\u01bf\u0007"+ - "\u0004\u0000\u0000\u01be\u01bc\u0001\u0000\u0000\u0000\u01be\u01bf\u0001"+ - "\u0000\u0000\u0000\u01bfC\u0001\u0000\u0000\u0000\u01c0\u01c1\u0005\t"+ - "\u0000\u0000\u01c1\u01c6\u00036\u001b\u0000\u01c2\u01c3\u0005%\u0000\u0000"+ - "\u01c3\u01c5\u00036\u001b\u0000\u01c4\u01c2\u0001\u0000\u0000\u0000\u01c5"+ - "\u01c8\u0001\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c6"+ - "\u01c7\u0001\u0000\u0000\u0000\u01c7E\u0001\u0000\u0000\u0000\u01c8\u01c6"+ - "\u0001\u0000\u0000\u0000\u01c9\u01ca\u0005\u0002\u0000\u0000\u01ca\u01cf"+ - "\u00036\u001b\u0000\u01cb\u01cc\u0005%\u0000\u0000\u01cc\u01ce\u00036"+ - "\u001b\u0000\u01cd\u01cb\u0001\u0000\u0000\u0000\u01ce\u01d1\u0001\u0000"+ - "\u0000\u0000\u01cf\u01cd\u0001\u0000\u0000\u0000\u01cf\u01d0\u0001\u0000"+ - "\u0000\u0000\u01d0G\u0001\u0000\u0000\u0000\u01d1\u01cf\u0001\u0000\u0000"+ - "\u0000\u01d2\u01d3\u0005\u000e\u0000\u0000\u01d3\u01d8\u0003J%\u0000\u01d4"+ - "\u01d5\u0005%\u0000\u0000\u01d5\u01d7\u0003J%\u0000\u01d6\u01d4\u0001"+ - "\u0000\u0000\u0000\u01d7\u01da\u0001\u0000\u0000\u0000\u01d8\u01d6\u0001"+ - "\u0000\u0000\u0000\u01d8\u01d9\u0001\u0000\u0000\u0000\u01d9I\u0001\u0000"+ - "\u0000\u0000\u01da\u01d8\u0001\u0000\u0000\u0000\u01db\u01dc\u00036\u001b"+ - "\u0000\u01dc\u01dd\u0005S\u0000\u0000\u01dd\u01de\u00036\u001b\u0000\u01de"+ - "K\u0001\u0000\u0000\u0000\u01df\u01e0\u0005\u0001\u0000\u0000\u01e0\u01e1"+ - "\u0003\u0012\t\u0000\u01e1\u01e3\u0003^/\u0000\u01e2\u01e4\u0003R)\u0000"+ - "\u01e3\u01e2\u0001\u0000\u0000\u0000\u01e3\u01e4\u0001\u0000\u0000\u0000"+ - "\u01e4M\u0001\u0000\u0000\u0000\u01e5\u01e6\u0005\u0007\u0000\u0000\u01e6"+ - "\u01e7\u0003\u0012\t\u0000\u01e7\u01e8\u0003^/\u0000\u01e8O\u0001\u0000"+ - "\u0000\u0000\u01e9\u01ea\u0005\r\u0000\u0000\u01ea\u01eb\u00034\u001a"+ - "\u0000\u01ebQ\u0001\u0000\u0000\u0000\u01ec\u01f1\u0003T*\u0000\u01ed"+ - "\u01ee\u0005%\u0000\u0000\u01ee\u01f0\u0003T*\u0000\u01ef\u01ed\u0001"+ - "\u0000\u0000\u0000\u01f0\u01f3\u0001\u0000\u0000\u0000\u01f1\u01ef\u0001"+ - "\u0000\u0000\u0000\u01f1\u01f2\u0001\u0000\u0000\u0000\u01f2S\u0001\u0000"+ - "\u0000\u0000\u01f3\u01f1\u0001\u0000\u0000\u0000\u01f4\u01f5\u00038\u001c"+ - "\u0000\u01f5\u01f6\u0005#\u0000\u0000\u01f6\u01f7\u0003<\u001e\u0000\u01f7"+ - "U\u0001\u0000\u0000\u0000\u01f8\u01f9\u0007\u0005\u0000\u0000\u01f9W\u0001"+ - "\u0000\u0000\u0000\u01fa\u01fd\u0003Z-\u0000\u01fb\u01fd\u0003\\.\u0000"+ - "\u01fc\u01fa\u0001\u0000\u0000\u0000\u01fc\u01fb\u0001\u0000\u0000\u0000"+ - "\u01fdY\u0001\u0000\u0000\u0000\u01fe\u0200\u0007\u0000\u0000\u0000\u01ff"+ - "\u01fe\u0001\u0000\u0000\u0000\u01ff\u0200\u0001\u0000\u0000\u0000\u0200"+ - "\u0201\u0001\u0000\u0000\u0000\u0201\u0202\u0005\u001f\u0000\u0000\u0202"+ - "[\u0001\u0000\u0000\u0000\u0203\u0205\u0007\u0000\u0000\u0000\u0204\u0203"+ - "\u0001\u0000\u0000\u0000\u0204\u0205\u0001\u0000\u0000\u0000\u0205\u0206"+ - "\u0001\u0000\u0000\u0000\u0206\u0207\u0005\u001e\u0000\u0000\u0207]\u0001"+ - "\u0000\u0000\u0000\u0208\u0209\u0005\u001d\u0000\u0000\u0209_\u0001\u0000"+ - "\u0000\u0000\u020a\u020b\u0007\u0006\u0000\u0000\u020ba\u0001\u0000\u0000"+ - "\u0000\u020c\u020d\u0005\u0005\u0000\u0000\u020d\u020e\u0003d2\u0000\u020e"+ - "c\u0001\u0000\u0000\u0000\u020f\u0210\u0005C\u0000\u0000\u0210\u0211\u0003"+ - "\u0002\u0001\u0000\u0211\u0212\u0005D\u0000\u0000\u0212e\u0001\u0000\u0000"+ - "\u0000\u0213\u0214\u0005\u0010\u0000\u0000\u0214\u0215\u0005c\u0000\u0000"+ - "\u0215g\u0001\u0000\u0000\u0000\u0216\u0217\u0005\u000b\u0000\u0000\u0217"+ - "\u0218\u0005g\u0000\u0000\u0218i\u0001\u0000\u0000\u0000\u0219\u021a\u0005"+ - "\u0003\u0000\u0000\u021a\u021d\u0005Y\u0000\u0000\u021b\u021c\u0005W\u0000"+ - "\u0000\u021c\u021e\u00036\u001b\u0000\u021d\u021b\u0001\u0000\u0000\u0000"+ - "\u021d\u021e\u0001\u0000\u0000\u0000\u021e\u0228\u0001\u0000\u0000\u0000"+ - "\u021f\u0220\u0005X\u0000\u0000\u0220\u0225\u0003l6\u0000\u0221\u0222"+ - "\u0005%\u0000\u0000\u0222\u0224\u0003l6\u0000\u0223\u0221\u0001\u0000"+ - "\u0000\u0000\u0224\u0227\u0001\u0000\u0000\u0000\u0225\u0223\u0001\u0000"+ - "\u0000\u0000\u0225\u0226\u0001\u0000\u0000\u0000\u0226\u0229\u0001\u0000"+ - "\u0000\u0000\u0227\u0225\u0001\u0000\u0000\u0000\u0228\u021f\u0001\u0000"+ - "\u0000\u0000\u0228\u0229\u0001\u0000\u0000\u0000\u0229k\u0001\u0000\u0000"+ - "\u0000\u022a\u022b\u00036\u001b\u0000\u022b\u022c\u0005#\u0000\u0000\u022c"+ - "\u022e\u0001\u0000\u0000\u0000\u022d\u022a\u0001\u0000\u0000\u0000\u022d"+ - "\u022e\u0001\u0000\u0000\u0000\u022e\u022f\u0001\u0000\u0000\u0000\u022f"+ - "\u0230\u00036\u001b\u0000\u0230m\u0001\u0000\u0000\u00007y\u0082\u0091"+ - "\u009d\u00a6\u00ae\u00b2\u00ba\u00bc\u00c1\u00c8\u00cd\u00d4\u00da\u00e2"+ - "\u00e4\u00ef\u00f6\u0101\u0104\u0112\u011a\u0122\u0126\u0129\u0133\u013c"+ - "\u0144\u0151\u0155\u0159\u0160\u0164\u016a\u0171\u0179\u018f\u019a\u01a5"+ - "\u01aa\u01b5\u01ba\u01be\u01c6\u01cf\u01d8\u01e3\u01f1\u01fc\u01ff\u0204"+ - "\u021d\u0225\u0228\u022d"; + "2\u00072\u00023\u00073\u00024\u00074\u0001\u0000\u0001\u0000\u0001\u0000"+ + "\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"+ + "\u0005\u0001t\b\u0001\n\u0001\f\u0001w\t\u0001\u0001\u0002\u0001\u0002"+ + "\u0001\u0002\u0001\u0002\u0001\u0002\u0001\u0002\u0003\u0002\u007f\b\u0002"+ + "\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003"+ + "\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003\u0001\u0003"+ + "\u0001\u0003\u0003\u0003\u008e\b\u0003\u0001\u0004\u0001\u0004\u0001\u0004"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0003\u0005\u009a\b\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0001\u0005\u0001\u0005\u0005\u0005\u00a1\b\u0005\n\u0005\f\u0005\u00a4"+ + "\t\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0003"+ + "\u0005\u00ab\b\u0005\u0001\u0005\u0001\u0005\u0003\u0005\u00af\b\u0005"+ + "\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005\u0001\u0005"+ + "\u0005\u0005\u00b7\b\u0005\n\u0005\f\u0005\u00ba\t\u0005\u0001\u0006\u0001"+ + "\u0006\u0003\u0006\u00be\b\u0006\u0001\u0006\u0001\u0006\u0001\u0006\u0001"+ + "\u0006\u0001\u0006\u0003\u0006\u00c5\b\u0006\u0001\u0006\u0001\u0006\u0001"+ + "\u0006\u0003\u0006\u00ca\b\u0006\u0001\u0007\u0001\u0007\u0001\u0007\u0001"+ + "\u0007\u0001\u0007\u0003\u0007\u00d1\b\u0007\u0001\b\u0001\b\u0001\b\u0001"+ + "\b\u0003\b\u00d7\b\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0001\b\u0005"+ + "\b\u00df\b\b\n\b\f\b\u00e2\t\b\u0001\t\u0001\t\u0001\t\u0001\t\u0001\t"+ + "\u0001\t\u0001\t\u0001\t\u0003\t\u00ec\b\t\u0001\t\u0001\t\u0001\t\u0005"+ + "\t\u00f1\b\t\n\t\f\t\u00f4\t\t\u0001\n\u0001\n\u0001\n\u0001\n\u0001\n"+ + "\u0001\n\u0005\n\u00fc\b\n\n\n\f\n\u00ff\t\n\u0003\n\u0101\b\n\u0001\n"+ + "\u0001\n\u0001\u000b\u0001\u000b\u0001\f\u0001\f\u0001\f\u0001\r\u0001"+ + "\r\u0001\r\u0005\r\u010d\b\r\n\r\f\r\u0110\t\r\u0001\u000e\u0001\u000e"+ + "\u0001\u000e\u0001\u000e\u0001\u000e\u0003\u000e\u0117\b\u000e\u0001\u000f"+ + "\u0001\u000f\u0001\u000f\u0001\u000f\u0005\u000f\u011d\b\u000f\n\u000f"+ + "\f\u000f\u0120\t\u000f\u0001\u000f\u0003\u000f\u0123\b\u000f\u0001\u0010"+ + "\u0001\u0010\u0001\u0011\u0001\u0011\u0003\u0011\u0129\b\u0011\u0001\u0012"+ + "\u0001\u0012\u0001\u0012\u0001\u0012\u0005\u0012\u012f\b\u0012\n\u0012"+ + "\f\u0012\u0132\t\u0012\u0001\u0013\u0001\u0013\u0001\u0013\u0001\u0013"+ + "\u0001\u0014\u0001\u0014\u0001\u0014\u0001\u0014\u0005\u0014\u013c\b\u0014"+ + "\n\u0014\f\u0014\u013f\t\u0014\u0001\u0014\u0003\u0014\u0142\b\u0014\u0001"+ + "\u0014\u0001\u0014\u0003\u0014\u0146\b\u0014\u0001\u0015\u0001\u0015\u0001"+ + "\u0015\u0001\u0016\u0001\u0016\u0003\u0016\u014d\b\u0016\u0001\u0016\u0001"+ + "\u0016\u0003\u0016\u0151\b\u0016\u0001\u0017\u0001\u0017\u0001\u0017\u0001"+ + "\u0017\u0003\u0017\u0157\b\u0017\u0001\u0018\u0001\u0018\u0001\u0018\u0005"+ + "\u0018\u015c\b\u0018\n\u0018\f\u0018\u015f\t\u0018\u0001\u0019\u0001\u0019"+ + "\u0001\u0019\u0005\u0019\u0164\b\u0019\n\u0019\f\u0019\u0167\t\u0019\u0001"+ + "\u001a\u0001\u001a\u0001\u001b\u0001\u001b\u0001\u001c\u0001\u001c\u0001"+ + "\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001"+ + "\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0005\u001c\u017a"+ + "\b\u001c\n\u001c\f\u001c\u017d\t\u001c\u0001\u001c\u0001\u001c\u0001\u001c"+ + "\u0001\u001c\u0001\u001c\u0001\u001c\u0005\u001c\u0185\b\u001c\n\u001c"+ + "\f\u001c\u0188\t\u001c\u0001\u001c\u0001\u001c\u0001\u001c\u0001\u001c"+ + "\u0001\u001c\u0001\u001c\u0005\u001c\u0190\b\u001c\n\u001c\f\u001c\u0193"+ + "\t\u001c\u0001\u001c\u0001\u001c\u0003\u001c\u0197\b\u001c\u0001\u001d"+ + "\u0001\u001d\u0001\u001d\u0001\u001e\u0001\u001e\u0001\u001e\u0001\u001e"+ + "\u0005\u001e\u01a0\b\u001e\n\u001e\f\u001e\u01a3\t\u001e\u0001\u001f\u0001"+ + "\u001f\u0003\u001f\u01a7\b\u001f\u0001\u001f\u0001\u001f\u0003\u001f\u01ab"+ + "\b\u001f\u0001 \u0001 \u0001 \u0001 \u0005 \u01b1\b \n \f \u01b4\t \u0001"+ + "!\u0001!\u0001!\u0001!\u0005!\u01ba\b!\n!\f!\u01bd\t!\u0001\"\u0001\""+ + "\u0001\"\u0001\"\u0005\"\u01c3\b\"\n\"\f\"\u01c6\t\"\u0001#\u0001#\u0001"+ + "#\u0001#\u0001$\u0001$\u0001$\u0001$\u0003$\u01d0\b$\u0001%\u0001%\u0001"+ + "%\u0001%\u0001&\u0001&\u0001&\u0001\'\u0001\'\u0001\'\u0005\'\u01dc\b"+ + "\'\n\'\f\'\u01df\t\'\u0001(\u0001(\u0001(\u0001(\u0001)\u0001)\u0001*"+ + "\u0001*\u0003*\u01e9\b*\u0001+\u0003+\u01ec\b+\u0001+\u0001+\u0001,\u0003"+ + ",\u01f1\b,\u0001,\u0001,\u0001-\u0001-\u0001.\u0001.\u0001/\u0001/\u0001"+ + "/\u00010\u00010\u00010\u00010\u00011\u00011\u00011\u00012\u00012\u0001"+ + "2\u00013\u00013\u00013\u00013\u00033\u020a\b3\u00013\u00013\u00013\u0001"+ + "3\u00053\u0210\b3\n3\f3\u0213\t3\u00033\u0215\b3\u00014\u00014\u00014"+ + "\u00034\u021a\b4\u00014\u00014\u00014\u0000\u0004\u0002\n\u0010\u0012"+ + "5\u0000\u0002\u0004\u0006\b\n\f\u000e\u0010\u0012\u0014\u0016\u0018\u001a"+ + "\u001c\u001e \"$&(*,.02468:<>@BDFHJLNPRTVXZ\\^`bdfh\u0000\u0007\u0001"+ + "\u0000>?\u0001\u0000@B\u0001\u0000EF\u0002\u0000\"\"&&\u0001\u0000)*\u0002"+ + "\u0000((66\u0002\u0000779=\u023a\u0000j\u0001\u0000\u0000\u0000\u0002"+ + "m\u0001\u0000\u0000\u0000\u0004~\u0001\u0000\u0000\u0000\u0006\u008d\u0001"+ + "\u0000\u0000\u0000\b\u008f\u0001\u0000\u0000\u0000\n\u00ae\u0001\u0000"+ + "\u0000\u0000\f\u00c9\u0001\u0000\u0000\u0000\u000e\u00d0\u0001\u0000\u0000"+ + "\u0000\u0010\u00d6\u0001\u0000\u0000\u0000\u0012\u00eb\u0001\u0000\u0000"+ + "\u0000\u0014\u00f5\u0001\u0000\u0000\u0000\u0016\u0104\u0001\u0000\u0000"+ + "\u0000\u0018\u0106\u0001\u0000\u0000\u0000\u001a\u0109\u0001\u0000\u0000"+ + "\u0000\u001c\u0116\u0001\u0000\u0000\u0000\u001e\u0118\u0001\u0000\u0000"+ + "\u0000 \u0124\u0001\u0000\u0000\u0000\"\u0128\u0001\u0000\u0000\u0000"+ + "$\u012a\u0001\u0000\u0000\u0000&\u0133\u0001\u0000\u0000\u0000(\u0137"+ + "\u0001\u0000\u0000\u0000*\u0147\u0001\u0000\u0000\u0000,\u014a\u0001\u0000"+ + "\u0000\u0000.\u0152\u0001\u0000\u0000\u00000\u0158\u0001\u0000\u0000\u0000"+ + "2\u0160\u0001\u0000\u0000\u00004\u0168\u0001\u0000\u0000\u00006\u016a"+ + "\u0001\u0000\u0000\u00008\u0196\u0001\u0000\u0000\u0000:\u0198\u0001\u0000"+ + "\u0000\u0000<\u019b\u0001\u0000\u0000\u0000>\u01a4\u0001\u0000\u0000\u0000"+ + "@\u01ac\u0001\u0000\u0000\u0000B\u01b5\u0001\u0000\u0000\u0000D\u01be"+ + "\u0001\u0000\u0000\u0000F\u01c7\u0001\u0000\u0000\u0000H\u01cb\u0001\u0000"+ + "\u0000\u0000J\u01d1\u0001\u0000\u0000\u0000L\u01d5\u0001\u0000\u0000\u0000"+ + "N\u01d8\u0001\u0000\u0000\u0000P\u01e0\u0001\u0000\u0000\u0000R\u01e4"+ + "\u0001\u0000\u0000\u0000T\u01e8\u0001\u0000\u0000\u0000V\u01eb\u0001\u0000"+ + "\u0000\u0000X\u01f0\u0001\u0000\u0000\u0000Z\u01f4\u0001\u0000\u0000\u0000"+ + "\\\u01f6\u0001\u0000\u0000\u0000^\u01f8\u0001\u0000\u0000\u0000`\u01fb"+ + "\u0001\u0000\u0000\u0000b\u01ff\u0001\u0000\u0000\u0000d\u0202\u0001\u0000"+ + "\u0000\u0000f\u0205\u0001\u0000\u0000\u0000h\u0219\u0001\u0000\u0000\u0000"+ + "jk\u0003\u0002\u0001\u0000kl\u0005\u0000\u0000\u0001l\u0001\u0001\u0000"+ + "\u0000\u0000mn\u0006\u0001\uffff\uffff\u0000no\u0003\u0004\u0002\u0000"+ + "ou\u0001\u0000\u0000\u0000pq\n\u0001\u0000\u0000qr\u0005\u001c\u0000\u0000"+ + "rt\u0003\u0006\u0003\u0000sp\u0001\u0000\u0000\u0000tw\u0001\u0000\u0000"+ + "\u0000us\u0001\u0000\u0000\u0000uv\u0001\u0000\u0000\u0000v\u0003\u0001"+ + "\u0000\u0000\u0000wu\u0001\u0000\u0000\u0000x\u007f\u0003^/\u0000y\u007f"+ + "\u0003\u001e\u000f\u0000z\u007f\u0003\u0018\f\u0000{\u007f\u0003(\u0014"+ + "\u0000|\u007f\u0003b1\u0000}\u007f\u0003d2\u0000~x\u0001\u0000\u0000\u0000"+ + "~y\u0001\u0000\u0000\u0000~z\u0001\u0000\u0000\u0000~{\u0001\u0000\u0000"+ + "\u0000~|\u0001\u0000\u0000\u0000~}\u0001\u0000\u0000\u0000\u007f\u0005"+ + "\u0001\u0000\u0000\u0000\u0080\u008e\u0003*\u0015\u0000\u0081\u008e\u0003"+ + ".\u0017\u0000\u0082\u008e\u0003:\u001d\u0000\u0083\u008e\u0003@ \u0000"+ + "\u0084\u008e\u0003<\u001e\u0000\u0085\u008e\u0003,\u0016\u0000\u0086\u008e"+ + "\u0003\b\u0004\u0000\u0087\u008e\u0003B!\u0000\u0088\u008e\u0003D\"\u0000"+ + "\u0089\u008e\u0003H$\u0000\u008a\u008e\u0003J%\u0000\u008b\u008e\u0003"+ + "f3\u0000\u008c\u008e\u0003L&\u0000\u008d\u0080\u0001\u0000\u0000\u0000"+ + "\u008d\u0081\u0001\u0000\u0000\u0000\u008d\u0082\u0001\u0000\u0000\u0000"+ + "\u008d\u0083\u0001\u0000\u0000\u0000\u008d\u0084\u0001\u0000\u0000\u0000"+ + "\u008d\u0085\u0001\u0000\u0000\u0000\u008d\u0086\u0001\u0000\u0000\u0000"+ + "\u008d\u0087\u0001\u0000\u0000\u0000\u008d\u0088\u0001\u0000\u0000\u0000"+ + "\u008d\u0089\u0001\u0000\u0000\u0000\u008d\u008a\u0001\u0000\u0000\u0000"+ + "\u008d\u008b\u0001\u0000\u0000\u0000\u008d\u008c\u0001\u0000\u0000\u0000"+ + "\u008e\u0007\u0001\u0000\u0000\u0000\u008f\u0090\u0005\u0013\u0000\u0000"+ + "\u0090\u0091\u0003\n\u0005\u0000\u0091\t\u0001\u0000\u0000\u0000\u0092"+ + "\u0093\u0006\u0005\uffff\uffff\u0000\u0093\u0094\u0005/\u0000\u0000\u0094"+ + "\u00af\u0003\n\u0005\u0007\u0095\u00af\u0003\u000e\u0007\u0000\u0096\u00af"+ + "\u0003\f\u0006\u0000\u0097\u0099\u0003\u000e\u0007\u0000\u0098\u009a\u0005"+ + "/\u0000\u0000\u0099\u0098\u0001\u0000\u0000\u0000\u0099\u009a\u0001\u0000"+ + "\u0000\u0000\u009a\u009b\u0001\u0000\u0000\u0000\u009b\u009c\u0005,\u0000"+ + "\u0000\u009c\u009d\u0005+\u0000\u0000\u009d\u00a2\u0003\u000e\u0007\u0000"+ + "\u009e\u009f\u0005%\u0000\u0000\u009f\u00a1\u0003\u000e\u0007\u0000\u00a0"+ + "\u009e\u0001\u0000\u0000\u0000\u00a1\u00a4\u0001\u0000\u0000\u0000\u00a2"+ + "\u00a0\u0001\u0000\u0000\u0000\u00a2\u00a3\u0001\u0000\u0000\u0000\u00a3"+ + "\u00a5\u0001\u0000\u0000\u0000\u00a4\u00a2\u0001\u0000\u0000\u0000\u00a5"+ + "\u00a6\u00055\u0000\u0000\u00a6\u00af\u0001\u0000\u0000\u0000\u00a7\u00a8"+ + "\u0003\u000e\u0007\u0000\u00a8\u00aa\u0005-\u0000\u0000\u00a9\u00ab\u0005"+ + "/\u0000\u0000\u00aa\u00a9\u0001\u0000\u0000\u0000\u00aa\u00ab\u0001\u0000"+ + "\u0000\u0000\u00ab\u00ac\u0001\u0000\u0000\u0000\u00ac\u00ad\u00050\u0000"+ + "\u0000\u00ad\u00af\u0001\u0000\u0000\u0000\u00ae\u0092\u0001\u0000\u0000"+ + "\u0000\u00ae\u0095\u0001\u0000\u0000\u0000\u00ae\u0096\u0001\u0000\u0000"+ + "\u0000\u00ae\u0097\u0001\u0000\u0000\u0000\u00ae\u00a7\u0001\u0000\u0000"+ + "\u0000\u00af\u00b8\u0001\u0000\u0000\u0000\u00b0\u00b1\n\u0004\u0000\u0000"+ + "\u00b1\u00b2\u0005!\u0000\u0000\u00b2\u00b7\u0003\n\u0005\u0005\u00b3"+ + "\u00b4\n\u0003\u0000\u0000\u00b4\u00b5\u00052\u0000\u0000\u00b5\u00b7"+ + "\u0003\n\u0005\u0004\u00b6\u00b0\u0001\u0000\u0000\u0000\u00b6\u00b3\u0001"+ + "\u0000\u0000\u0000\u00b7\u00ba\u0001\u0000\u0000\u0000\u00b8\u00b6\u0001"+ + "\u0000\u0000\u0000\u00b8\u00b9\u0001\u0000\u0000\u0000\u00b9\u000b\u0001"+ + "\u0000\u0000\u0000\u00ba\u00b8\u0001\u0000\u0000\u0000\u00bb\u00bd\u0003"+ + "\u000e\u0007\u0000\u00bc\u00be\u0005/\u0000\u0000\u00bd\u00bc\u0001\u0000"+ + "\u0000\u0000\u00bd\u00be\u0001\u0000\u0000\u0000\u00be\u00bf\u0001\u0000"+ + "\u0000\u0000\u00bf\u00c0\u0005.\u0000\u0000\u00c0\u00c1\u0003Z-\u0000"+ + "\u00c1\u00ca\u0001\u0000\u0000\u0000\u00c2\u00c4\u0003\u000e\u0007\u0000"+ + "\u00c3\u00c5\u0005/\u0000\u0000\u00c4\u00c3\u0001\u0000\u0000\u0000\u00c4"+ + "\u00c5\u0001\u0000\u0000\u0000\u00c5\u00c6\u0001\u0000\u0000\u0000\u00c6"+ + "\u00c7\u00054\u0000\u0000\u00c7\u00c8\u0003Z-\u0000\u00c8\u00ca\u0001"+ + "\u0000\u0000\u0000\u00c9\u00bb\u0001\u0000\u0000\u0000\u00c9\u00c2\u0001"+ + "\u0000\u0000\u0000\u00ca\r\u0001\u0000\u0000\u0000\u00cb\u00d1\u0003\u0010"+ + "\b\u0000\u00cc\u00cd\u0003\u0010\b\u0000\u00cd\u00ce\u0003\\.\u0000\u00ce"+ + "\u00cf\u0003\u0010\b\u0000\u00cf\u00d1\u0001\u0000\u0000\u0000\u00d0\u00cb"+ + "\u0001\u0000\u0000\u0000\u00d0\u00cc\u0001\u0000\u0000\u0000\u00d1\u000f"+ + "\u0001\u0000\u0000\u0000\u00d2\u00d3\u0006\b\uffff\uffff\u0000\u00d3\u00d7"+ + "\u0003\u0012\t\u0000\u00d4\u00d5\u0007\u0000\u0000\u0000\u00d5\u00d7\u0003"+ + "\u0010\b\u0003\u00d6\u00d2\u0001\u0000\u0000\u0000\u00d6\u00d4\u0001\u0000"+ + "\u0000\u0000\u00d7\u00e0\u0001\u0000\u0000\u0000\u00d8\u00d9\n\u0002\u0000"+ + "\u0000\u00d9\u00da\u0007\u0001\u0000\u0000\u00da\u00df\u0003\u0010\b\u0003"+ + "\u00db\u00dc\n\u0001\u0000\u0000\u00dc\u00dd\u0007\u0000\u0000\u0000\u00dd"+ + "\u00df\u0003\u0010\b\u0002\u00de\u00d8\u0001\u0000\u0000\u0000\u00de\u00db"+ + "\u0001\u0000\u0000\u0000\u00df\u00e2\u0001\u0000\u0000\u0000\u00e0\u00de"+ + "\u0001\u0000\u0000\u0000\u00e0\u00e1\u0001\u0000\u0000\u0000\u00e1\u0011"+ + "\u0001\u0000\u0000\u0000\u00e2\u00e0\u0001\u0000\u0000\u0000\u00e3\u00e4"+ + "\u0006\t\uffff\uffff\u0000\u00e4\u00ec\u00038\u001c\u0000\u00e5\u00ec"+ + "\u00030\u0018\u0000\u00e6\u00ec\u0003\u0014\n\u0000\u00e7\u00e8\u0005"+ + "+\u0000\u0000\u00e8\u00e9\u0003\n\u0005\u0000\u00e9\u00ea\u00055\u0000"+ + "\u0000\u00ea\u00ec\u0001\u0000\u0000\u0000\u00eb\u00e3\u0001\u0000\u0000"+ + "\u0000\u00eb\u00e5\u0001\u0000\u0000\u0000\u00eb\u00e6\u0001\u0000\u0000"+ + "\u0000\u00eb\u00e7\u0001\u0000\u0000\u0000\u00ec\u00f2\u0001\u0000\u0000"+ + "\u0000\u00ed\u00ee\n\u0001\u0000\u0000\u00ee\u00ef\u0005$\u0000\u0000"+ + "\u00ef\u00f1\u0003\u0016\u000b\u0000\u00f0\u00ed\u0001\u0000\u0000\u0000"+ + "\u00f1\u00f4\u0001\u0000\u0000\u0000\u00f2\u00f0\u0001\u0000\u0000\u0000"+ + "\u00f2\u00f3\u0001\u0000\u0000\u0000\u00f3\u0013\u0001\u0000\u0000\u0000"+ + "\u00f4\u00f2\u0001\u0000\u0000\u0000\u00f5\u00f6\u00034\u001a\u0000\u00f6"+ + "\u0100\u0005+\u0000\u0000\u00f7\u0101\u0005@\u0000\u0000\u00f8\u00fd\u0003"+ + "\n\u0005\u0000\u00f9\u00fa\u0005%\u0000\u0000\u00fa\u00fc\u0003\n\u0005"+ + "\u0000\u00fb\u00f9\u0001\u0000\u0000\u0000\u00fc\u00ff\u0001\u0000\u0000"+ + "\u0000\u00fd\u00fb\u0001\u0000\u0000\u0000\u00fd\u00fe\u0001\u0000\u0000"+ + "\u0000\u00fe\u0101\u0001\u0000\u0000\u0000\u00ff\u00fd\u0001\u0000\u0000"+ + "\u0000\u0100\u00f7\u0001\u0000\u0000\u0000\u0100\u00f8\u0001\u0000\u0000"+ + "\u0000\u0100\u0101\u0001\u0000\u0000\u0000\u0101\u0102\u0001\u0000\u0000"+ + "\u0000\u0102\u0103\u00055\u0000\u0000\u0103\u0015\u0001\u0000\u0000\u0000"+ + "\u0104\u0105\u00034\u001a\u0000\u0105\u0017\u0001\u0000\u0000\u0000\u0106"+ + "\u0107\u0005\u000f\u0000\u0000\u0107\u0108\u0003\u001a\r\u0000\u0108\u0019"+ + "\u0001\u0000\u0000\u0000\u0109\u010e\u0003\u001c\u000e\u0000\u010a\u010b"+ + "\u0005%\u0000\u0000\u010b\u010d\u0003\u001c\u000e\u0000\u010c\u010a\u0001"+ + "\u0000\u0000\u0000\u010d\u0110\u0001\u0000\u0000\u0000\u010e\u010c\u0001"+ + "\u0000\u0000\u0000\u010e\u010f\u0001\u0000\u0000\u0000\u010f\u001b\u0001"+ + "\u0000\u0000\u0000\u0110\u010e\u0001\u0000\u0000\u0000\u0111\u0117\u0003"+ + "\n\u0005\u0000\u0112\u0113\u00030\u0018\u0000\u0113\u0114\u0005#\u0000"+ + "\u0000\u0114\u0115\u0003\n\u0005\u0000\u0115\u0117\u0001\u0000\u0000\u0000"+ + "\u0116\u0111\u0001\u0000\u0000\u0000\u0116\u0112\u0001\u0000\u0000\u0000"+ + "\u0117\u001d\u0001\u0000\u0000\u0000\u0118\u0119\u0005\u0006\u0000\u0000"+ + "\u0119\u011e\u0003 \u0010\u0000\u011a\u011b\u0005%\u0000\u0000\u011b\u011d"+ + "\u0003 \u0010\u0000\u011c\u011a\u0001\u0000\u0000\u0000\u011d\u0120\u0001"+ + "\u0000\u0000\u0000\u011e\u011c\u0001\u0000\u0000\u0000\u011e\u011f\u0001"+ + "\u0000\u0000\u0000\u011f\u0122\u0001\u0000\u0000\u0000\u0120\u011e\u0001"+ + "\u0000\u0000\u0000\u0121\u0123\u0003\"\u0011\u0000\u0122\u0121\u0001\u0000"+ + "\u0000\u0000\u0122\u0123\u0001\u0000\u0000\u0000\u0123\u001f\u0001\u0000"+ + "\u0000\u0000\u0124\u0125\u0005\u0018\u0000\u0000\u0125!\u0001\u0000\u0000"+ + "\u0000\u0126\u0129\u0003$\u0012\u0000\u0127\u0129\u0003&\u0013\u0000\u0128"+ + "\u0126\u0001\u0000\u0000\u0000\u0128\u0127\u0001\u0000\u0000\u0000\u0129"+ + "#\u0001\u0000\u0000\u0000\u012a\u012b\u0005J\u0000\u0000\u012b\u0130\u0003"+ + " \u0010\u0000\u012c\u012d\u0005%\u0000\u0000\u012d\u012f\u0003 \u0010"+ + "\u0000\u012e\u012c\u0001\u0000\u0000\u0000\u012f\u0132\u0001\u0000\u0000"+ + "\u0000\u0130\u012e\u0001\u0000\u0000\u0000\u0130\u0131\u0001\u0000\u0000"+ + "\u0000\u0131%\u0001\u0000\u0000\u0000\u0132\u0130\u0001\u0000\u0000\u0000"+ + "\u0133\u0134\u0005C\u0000\u0000\u0134\u0135\u0003$\u0012\u0000\u0135\u0136"+ + "\u0005D\u0000\u0000\u0136\'\u0001\u0000\u0000\u0000\u0137\u0138\u0005"+ + "\f\u0000\u0000\u0138\u013d\u0003 \u0010\u0000\u0139\u013a\u0005%\u0000"+ + "\u0000\u013a\u013c\u0003 \u0010\u0000\u013b\u0139\u0001\u0000\u0000\u0000"+ + "\u013c\u013f\u0001\u0000\u0000\u0000\u013d\u013b\u0001\u0000\u0000\u0000"+ + "\u013d\u013e\u0001\u0000\u0000\u0000\u013e\u0141\u0001\u0000\u0000\u0000"+ + "\u013f\u013d\u0001\u0000\u0000\u0000\u0140\u0142\u0003\u001a\r\u0000\u0141"+ + "\u0140\u0001\u0000\u0000\u0000\u0141\u0142\u0001\u0000\u0000\u0000\u0142"+ + "\u0145\u0001\u0000\u0000\u0000\u0143\u0144\u0005 \u0000\u0000\u0144\u0146"+ + "\u0003\u001a\r\u0000\u0145\u0143\u0001\u0000\u0000\u0000\u0145\u0146\u0001"+ + "\u0000\u0000\u0000\u0146)\u0001\u0000\u0000\u0000\u0147\u0148\u0005\u0004"+ + "\u0000\u0000\u0148\u0149\u0003\u001a\r\u0000\u0149+\u0001\u0000\u0000"+ + "\u0000\u014a\u014c\u0005\u0012\u0000\u0000\u014b\u014d\u0003\u001a\r\u0000"+ + "\u014c\u014b\u0001\u0000\u0000\u0000\u014c\u014d\u0001\u0000\u0000\u0000"+ + "\u014d\u0150\u0001\u0000\u0000\u0000\u014e\u014f\u0005 \u0000\u0000\u014f"+ + "\u0151\u0003\u001a\r\u0000\u0150\u014e\u0001\u0000\u0000\u0000\u0150\u0151"+ + "\u0001\u0000\u0000\u0000\u0151-\u0001\u0000\u0000\u0000\u0152\u0153\u0005"+ + "\b\u0000\u0000\u0153\u0156\u0003\u001a\r\u0000\u0154\u0155\u0005 \u0000"+ + "\u0000\u0155\u0157\u0003\u001a\r\u0000\u0156\u0154\u0001\u0000\u0000\u0000"+ + "\u0156\u0157\u0001\u0000\u0000\u0000\u0157/\u0001\u0000\u0000\u0000\u0158"+ + "\u015d\u00034\u001a\u0000\u0159\u015a\u0005\'\u0000\u0000\u015a\u015c"+ + "\u00034\u001a\u0000\u015b\u0159\u0001\u0000\u0000\u0000\u015c\u015f\u0001"+ + "\u0000\u0000\u0000\u015d\u015b\u0001\u0000\u0000\u0000\u015d\u015e\u0001"+ + "\u0000\u0000\u0000\u015e1\u0001\u0000\u0000\u0000\u015f\u015d\u0001\u0000"+ + "\u0000\u0000\u0160\u0165\u00036\u001b\u0000\u0161\u0162\u0005\'\u0000"+ + "\u0000\u0162\u0164\u00036\u001b\u0000\u0163\u0161\u0001\u0000\u0000\u0000"+ + "\u0164\u0167\u0001\u0000\u0000\u0000\u0165\u0163\u0001\u0000\u0000\u0000"+ + "\u0165\u0166\u0001\u0000\u0000\u0000\u01663\u0001\u0000\u0000\u0000\u0167"+ + "\u0165\u0001\u0000\u0000\u0000\u0168\u0169\u0007\u0002\u0000\u0000\u0169"+ + "5\u0001\u0000\u0000\u0000\u016a\u016b\u0005N\u0000\u0000\u016b7\u0001"+ + "\u0000\u0000\u0000\u016c\u0197\u00050\u0000\u0000\u016d\u016e\u0003X,"+ + "\u0000\u016e\u016f\u0005E\u0000\u0000\u016f\u0197\u0001\u0000\u0000\u0000"+ + "\u0170\u0197\u0003V+\u0000\u0171\u0197\u0003X,\u0000\u0172\u0197\u0003"+ + "R)\u0000\u0173\u0197\u00053\u0000\u0000\u0174\u0197\u0003Z-\u0000\u0175"+ + "\u0176\u0005C\u0000\u0000\u0176\u017b\u0003T*\u0000\u0177\u0178\u0005"+ + "%\u0000\u0000\u0178\u017a\u0003T*\u0000\u0179\u0177\u0001\u0000\u0000"+ + "\u0000\u017a\u017d\u0001\u0000\u0000\u0000\u017b\u0179\u0001\u0000\u0000"+ + "\u0000\u017b\u017c\u0001\u0000\u0000\u0000\u017c\u017e\u0001\u0000\u0000"+ + "\u0000\u017d\u017b\u0001\u0000\u0000\u0000\u017e\u017f\u0005D\u0000\u0000"+ + "\u017f\u0197\u0001\u0000\u0000\u0000\u0180\u0181\u0005C\u0000\u0000\u0181"+ + "\u0186\u0003R)\u0000\u0182\u0183\u0005%\u0000\u0000\u0183\u0185\u0003"+ + "R)\u0000\u0184\u0182\u0001\u0000\u0000\u0000\u0185\u0188\u0001\u0000\u0000"+ + "\u0000\u0186\u0184\u0001\u0000\u0000\u0000\u0186\u0187\u0001\u0000\u0000"+ + "\u0000\u0187\u0189\u0001\u0000\u0000\u0000\u0188\u0186\u0001\u0000\u0000"+ + "\u0000\u0189\u018a\u0005D\u0000\u0000\u018a\u0197\u0001\u0000\u0000\u0000"+ + "\u018b\u018c\u0005C\u0000\u0000\u018c\u0191\u0003Z-\u0000\u018d\u018e"+ + "\u0005%\u0000\u0000\u018e\u0190\u0003Z-\u0000\u018f\u018d\u0001\u0000"+ + "\u0000\u0000\u0190\u0193\u0001\u0000\u0000\u0000\u0191\u018f\u0001\u0000"+ + "\u0000\u0000\u0191\u0192\u0001\u0000\u0000\u0000\u0192\u0194\u0001\u0000"+ + "\u0000\u0000\u0193\u0191\u0001\u0000\u0000\u0000\u0194\u0195\u0005D\u0000"+ + "\u0000\u0195\u0197\u0001\u0000\u0000\u0000\u0196\u016c\u0001\u0000\u0000"+ + "\u0000\u0196\u016d\u0001\u0000\u0000\u0000\u0196\u0170\u0001\u0000\u0000"+ + "\u0000\u0196\u0171\u0001\u0000\u0000\u0000\u0196\u0172\u0001\u0000\u0000"+ + "\u0000\u0196\u0173\u0001\u0000\u0000\u0000\u0196\u0174\u0001\u0000\u0000"+ + "\u0000\u0196\u0175\u0001\u0000\u0000\u0000\u0196\u0180\u0001\u0000\u0000"+ + "\u0000\u0196\u018b\u0001\u0000\u0000\u0000\u01979\u0001\u0000\u0000\u0000"+ + "\u0198\u0199\u0005\n\u0000\u0000\u0199\u019a\u0005\u001e\u0000\u0000\u019a"+ + ";\u0001\u0000\u0000\u0000\u019b\u019c\u0005\u0011\u0000\u0000\u019c\u01a1"+ + "\u0003>\u001f\u0000\u019d\u019e\u0005%\u0000\u0000\u019e\u01a0\u0003>"+ + "\u001f\u0000\u019f\u019d\u0001\u0000\u0000\u0000\u01a0\u01a3\u0001\u0000"+ + "\u0000\u0000\u01a1\u019f\u0001\u0000\u0000\u0000\u01a1\u01a2\u0001\u0000"+ + "\u0000\u0000\u01a2=\u0001\u0000\u0000\u0000\u01a3\u01a1\u0001\u0000\u0000"+ + "\u0000\u01a4\u01a6\u0003\n\u0005\u0000\u01a5\u01a7\u0007\u0003\u0000\u0000"+ + "\u01a6\u01a5\u0001\u0000\u0000\u0000\u01a6\u01a7\u0001\u0000\u0000\u0000"+ + "\u01a7\u01aa\u0001\u0000\u0000\u0000\u01a8\u01a9\u00051\u0000\u0000\u01a9"+ + "\u01ab\u0007\u0004\u0000\u0000\u01aa\u01a8\u0001\u0000\u0000\u0000\u01aa"+ + "\u01ab\u0001\u0000\u0000\u0000\u01ab?\u0001\u0000\u0000\u0000\u01ac\u01ad"+ + "\u0005\t\u0000\u0000\u01ad\u01b2\u00032\u0019\u0000\u01ae\u01af\u0005"+ + "%\u0000\u0000\u01af\u01b1\u00032\u0019\u0000\u01b0\u01ae\u0001\u0000\u0000"+ + "\u0000\u01b1\u01b4\u0001\u0000\u0000\u0000\u01b2\u01b0\u0001\u0000\u0000"+ + "\u0000\u01b2\u01b3\u0001\u0000\u0000\u0000\u01b3A\u0001\u0000\u0000\u0000"+ + "\u01b4\u01b2\u0001\u0000\u0000\u0000\u01b5\u01b6\u0005\u0002\u0000\u0000"+ + "\u01b6\u01bb\u00032\u0019\u0000\u01b7\u01b8\u0005%\u0000\u0000\u01b8\u01ba"+ + "\u00032\u0019\u0000\u01b9\u01b7\u0001\u0000\u0000\u0000\u01ba\u01bd\u0001"+ + "\u0000\u0000\u0000\u01bb\u01b9\u0001\u0000\u0000\u0000\u01bb\u01bc\u0001"+ + "\u0000\u0000\u0000\u01bcC\u0001\u0000\u0000\u0000\u01bd\u01bb\u0001\u0000"+ + "\u0000\u0000\u01be\u01bf\u0005\u000e\u0000\u0000\u01bf\u01c4\u0003F#\u0000"+ + "\u01c0\u01c1\u0005%\u0000\u0000\u01c1\u01c3\u0003F#\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\u01c5E\u0001"+ + "\u0000\u0000\u0000\u01c6\u01c4\u0001\u0000\u0000\u0000\u01c7\u01c8\u0003"+ + "2\u0019\u0000\u01c8\u01c9\u0005R\u0000\u0000\u01c9\u01ca\u00032\u0019"+ + "\u0000\u01caG\u0001\u0000\u0000\u0000\u01cb\u01cc\u0005\u0001\u0000\u0000"+ + "\u01cc\u01cd\u0003\u0012\t\u0000\u01cd\u01cf\u0003Z-\u0000\u01ce\u01d0"+ + "\u0003N\'\u0000\u01cf\u01ce\u0001\u0000\u0000\u0000\u01cf\u01d0\u0001"+ + "\u0000\u0000\u0000\u01d0I\u0001\u0000\u0000\u0000\u01d1\u01d2\u0005\u0007"+ + "\u0000\u0000\u01d2\u01d3\u0003\u0012\t\u0000\u01d3\u01d4\u0003Z-\u0000"+ + "\u01d4K\u0001\u0000\u0000\u0000\u01d5\u01d6\u0005\r\u0000\u0000\u01d6"+ + "\u01d7\u00030\u0018\u0000\u01d7M\u0001\u0000\u0000\u0000\u01d8\u01dd\u0003"+ + "P(\u0000\u01d9\u01da\u0005%\u0000\u0000\u01da\u01dc\u0003P(\u0000\u01db"+ + "\u01d9\u0001\u0000\u0000\u0000\u01dc\u01df\u0001\u0000\u0000\u0000\u01dd"+ + "\u01db\u0001\u0000\u0000\u0000\u01dd\u01de\u0001\u0000\u0000\u0000\u01de"+ + "O\u0001\u0000\u0000\u0000\u01df\u01dd\u0001\u0000\u0000\u0000\u01e0\u01e1"+ + "\u00034\u001a\u0000\u01e1\u01e2\u0005#\u0000\u0000\u01e2\u01e3\u00038"+ + "\u001c\u0000\u01e3Q\u0001\u0000\u0000\u0000\u01e4\u01e5\u0007\u0005\u0000"+ + "\u0000\u01e5S\u0001\u0000\u0000\u0000\u01e6\u01e9\u0003V+\u0000\u01e7"+ + "\u01e9\u0003X,\u0000\u01e8\u01e6\u0001\u0000\u0000\u0000\u01e8\u01e7\u0001"+ + "\u0000\u0000\u0000\u01e9U\u0001\u0000\u0000\u0000\u01ea\u01ec\u0007\u0000"+ + "\u0000\u0000\u01eb\u01ea\u0001\u0000\u0000\u0000\u01eb\u01ec\u0001\u0000"+ + "\u0000\u0000\u01ec\u01ed\u0001\u0000\u0000\u0000\u01ed\u01ee\u0005\u001f"+ + "\u0000\u0000\u01eeW\u0001\u0000\u0000\u0000\u01ef\u01f1\u0007\u0000\u0000"+ + "\u0000\u01f0\u01ef\u0001\u0000\u0000\u0000\u01f0\u01f1\u0001\u0000\u0000"+ + "\u0000\u01f1\u01f2\u0001\u0000\u0000\u0000\u01f2\u01f3\u0005\u001e\u0000"+ + "\u0000\u01f3Y\u0001\u0000\u0000\u0000\u01f4\u01f5\u0005\u001d\u0000\u0000"+ + "\u01f5[\u0001\u0000\u0000\u0000\u01f6\u01f7\u0007\u0006\u0000\u0000\u01f7"+ + "]\u0001\u0000\u0000\u0000\u01f8\u01f9\u0005\u0005\u0000\u0000\u01f9\u01fa"+ + "\u0003`0\u0000\u01fa_\u0001\u0000\u0000\u0000\u01fb\u01fc\u0005C\u0000"+ + "\u0000\u01fc\u01fd\u0003\u0002\u0001\u0000\u01fd\u01fe\u0005D\u0000\u0000"+ + "\u01fea\u0001\u0000\u0000\u0000\u01ff\u0200\u0005\u0010\u0000\u0000\u0200"+ + "\u0201\u0005b\u0000\u0000\u0201c\u0001\u0000\u0000\u0000\u0202\u0203\u0005"+ + "\u000b\u0000\u0000\u0203\u0204\u0005f\u0000\u0000\u0204e\u0001\u0000\u0000"+ + "\u0000\u0205\u0206\u0005\u0003\u0000\u0000\u0206\u0209\u0005X\u0000\u0000"+ + "\u0207\u0208\u0005V\u0000\u0000\u0208\u020a\u00032\u0019\u0000\u0209\u0207"+ + "\u0001\u0000\u0000\u0000\u0209\u020a\u0001\u0000\u0000\u0000\u020a\u0214"+ + "\u0001\u0000\u0000\u0000\u020b\u020c\u0005W\u0000\u0000\u020c\u0211\u0003"+ + "h4\u0000\u020d\u020e\u0005%\u0000\u0000\u020e\u0210\u0003h4\u0000\u020f"+ + "\u020d\u0001\u0000\u0000\u0000\u0210\u0213\u0001\u0000\u0000\u0000\u0211"+ + "\u020f\u0001\u0000\u0000\u0000\u0211\u0212\u0001\u0000\u0000\u0000\u0212"+ + "\u0215\u0001\u0000\u0000\u0000\u0213\u0211\u0001\u0000\u0000\u0000\u0214"+ + "\u020b\u0001\u0000\u0000\u0000\u0214\u0215\u0001\u0000\u0000\u0000\u0215"+ + "g\u0001\u0000\u0000\u0000\u0216\u0217\u00032\u0019\u0000\u0217\u0218\u0005"+ + "#\u0000\u0000\u0218\u021a\u0001\u0000\u0000\u0000\u0219\u0216\u0001\u0000"+ + "\u0000\u0000\u0219\u021a\u0001\u0000\u0000\u0000\u021a\u021b\u0001\u0000"+ + "\u0000\u0000\u021b\u021c\u00032\u0019\u0000\u021ci\u0001\u0000\u0000\u0000"+ + "5u~\u008d\u0099\u00a2\u00aa\u00ae\u00b6\u00b8\u00bd\u00c4\u00c9\u00d0"+ + "\u00d6\u00de\u00e0\u00eb\u00f2\u00fd\u0100\u010e\u0116\u011e\u0122\u0128"+ + "\u0130\u013d\u0141\u0145\u014c\u0150\u0156\u015d\u0165\u017b\u0186\u0191"+ + "\u0196\u01a1\u01a6\u01aa\u01b2\u01bb\u01c4\u01cf\u01dd\u01e8\u01eb\u01f0"+ + "\u0209\u0211\u0214\u0219"; public static final ATN _ATN = new ATNDeserializer().deserialize(_serializedATN.toCharArray()); static { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java index 92c9793fd8d9a..e77e0294bba0b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseListener.java @@ -372,30 +372,6 @@ public class EsqlBaseParserBaseListener implements EsqlBaseParserListener { *

      The default implementation does nothing.

      */ @Override public void exitIndexIdentifier(EsqlBaseParser.IndexIdentifierContext ctx) { } - /** - * {@inheritDoc} - * - *

      The default implementation does nothing.

      - */ - @Override public void enterFromOptions(EsqlBaseParser.FromOptionsContext ctx) { } - /** - * {@inheritDoc} - * - *

      The default implementation does nothing.

      - */ - @Override public void exitFromOptions(EsqlBaseParser.FromOptionsContext ctx) { } - /** - * {@inheritDoc} - * - *

      The default implementation does nothing.

      - */ - @Override public void enterConfigOption(EsqlBaseParser.ConfigOptionContext ctx) { } - /** - * {@inheritDoc} - * - *

      The default implementation does nothing.

      - */ - @Override public void exitConfigOption(EsqlBaseParser.ConfigOptionContext ctx) { } /** * {@inheritDoc} * diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java index 25eb59648fe6f..66308d91dce19 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserBaseVisitor.java @@ -222,20 +222,6 @@ public class EsqlBaseParserBaseVisitor extends AbstractParseTreeVisitor im * {@link #visitChildren} on {@code ctx}.

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

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

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

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

      - */ - @Override public T visitConfigOption(EsqlBaseParser.ConfigOptionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} * diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java index ac4047ffbd22f..978ac68670752 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserListener.java @@ -345,26 +345,6 @@ public interface EsqlBaseParserListener extends ParseTreeListener { * @param ctx the parse tree */ void exitIndexIdentifier(EsqlBaseParser.IndexIdentifierContext ctx); - /** - * Enter a parse tree produced by {@link EsqlBaseParser#fromOptions}. - * @param ctx the parse tree - */ - void enterFromOptions(EsqlBaseParser.FromOptionsContext ctx); - /** - * Exit a parse tree produced by {@link EsqlBaseParser#fromOptions}. - * @param ctx the parse tree - */ - void exitFromOptions(EsqlBaseParser.FromOptionsContext ctx); - /** - * Enter a parse tree produced by {@link EsqlBaseParser#configOption}. - * @param ctx the parse tree - */ - void enterConfigOption(EsqlBaseParser.ConfigOptionContext ctx); - /** - * Exit a parse tree produced by {@link EsqlBaseParser#configOption}. - * @param ctx the parse tree - */ - void exitConfigOption(EsqlBaseParser.ConfigOptionContext ctx); /** * Enter a parse tree produced by {@link EsqlBaseParser#metadata}. * @param ctx the parse tree diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java index 37b94cd585c11..bd24afcd28c4a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParserVisitor.java @@ -209,18 +209,6 @@ public interface EsqlBaseParserVisitor extends ParseTreeVisitor { * @return the visitor result */ T visitIndexIdentifier(EsqlBaseParser.IndexIdentifierContext ctx); - /** - * Visit a parse tree produced by {@link EsqlBaseParser#fromOptions}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitFromOptions(EsqlBaseParser.FromOptionsContext ctx); - /** - * Visit a parse tree produced by {@link EsqlBaseParser#configOption}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitConfigOption(EsqlBaseParser.ConfigOptionContext ctx); /** * Visit a parse tree produced by {@link EsqlBaseParser#metadata}. * @param ctx the parse tree diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java index b8fc29e4ef64d..1365f1698176f 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 @@ -47,7 +47,6 @@ import org.elasticsearch.xpack.ql.expression.UnresolvedAttribute; import org.elasticsearch.xpack.ql.expression.UnresolvedStar; import org.elasticsearch.xpack.ql.expression.function.UnresolvedFunction; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.parser.ParserUtils; import org.elasticsearch.xpack.ql.plan.TableIdentifier; import org.elasticsearch.xpack.ql.plan.logical.Filter; @@ -235,21 +234,7 @@ public LogicalPlan visitFromCommand(EsqlBaseParser.FromCommandContext ctx) { } } } - EsSourceOptions esSourceOptions = new EsSourceOptions(); - if (ctx.fromOptions() != null) { - for (var o : ctx.fromOptions().configOption()) { - var nameContext = o.string().get(0); - String name = visitString(nameContext).fold().toString(); - String value = visitString(o.string().get(1)).fold().toString(); - try { - esSourceOptions.addOption(name, value); - } catch (IllegalArgumentException iae) { - var cause = iae.getCause() != null ? ". " + iae.getCause().getMessage() : ""; - throw new ParsingException(iae, source(nameContext), "invalid options provided: " + iae.getMessage() + cause); - } - } - } - return new EsqlUnresolvedRelation(source, table, Arrays.asList(metadataMap.values().toArray(Attribute[]::new)), esSourceOptions); + return new EsqlUnresolvedRelation(source, table, Arrays.asList(metadataMap.values().toArray(Attribute[]::new))); } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsRelation.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsRelation.java index 19c3d9cf52109..52535beec2bfa 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsRelation.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsRelation.java @@ -9,7 +9,6 @@ import org.elasticsearch.xpack.ql.expression.Attribute; import org.elasticsearch.xpack.ql.expression.FieldAttribute; import org.elasticsearch.xpack.ql.index.EsIndex; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.plan.logical.LeafPlan; import org.elasticsearch.xpack.ql.tree.NodeInfo; import org.elasticsearch.xpack.ql.tree.NodeUtils; @@ -26,33 +25,26 @@ public class EsRelation extends LeafPlan { private final EsIndex index; private final List attrs; - private final EsSourceOptions esSourceOptions; private final boolean frozen; public EsRelation(Source source, EsIndex index, boolean frozen) { - this(source, index, flatten(source, index.mapping()), EsSourceOptions.NO_OPTIONS, frozen); + this(source, index, flatten(source, index.mapping()), frozen); } public EsRelation(Source source, EsIndex index, List attributes) { - this(source, index, attributes, EsSourceOptions.NO_OPTIONS, false); + this(source, index, attributes, false); } - public EsRelation(Source source, EsIndex index, List attributes, EsSourceOptions esSourceOptions) { - this(source, index, attributes, esSourceOptions, false); - } - - public EsRelation(Source source, EsIndex index, List attributes, EsSourceOptions esSourceOptions, boolean frozen) { + public EsRelation(Source source, EsIndex index, List attributes, boolean frozen) { super(source); this.index = index; this.attrs = attributes; - Objects.requireNonNull(esSourceOptions); - this.esSourceOptions = esSourceOptions; this.frozen = frozen; } @Override protected NodeInfo info() { - return NodeInfo.create(this, EsRelation::new, index, attrs, esSourceOptions, frozen); + return NodeInfo.create(this, EsRelation::new, index, attrs, frozen); } private static List flatten(Source source, Map mapping) { @@ -82,10 +74,6 @@ public EsIndex index() { return index; } - public EsSourceOptions esSourceOptions() { - return esSourceOptions; - } - public boolean frozen() { return frozen; } @@ -102,7 +90,7 @@ public boolean expressionsResolved() { @Override public int hashCode() { - return Objects.hash(index, esSourceOptions, frozen); + return Objects.hash(index, frozen); } @Override @@ -116,7 +104,7 @@ public boolean equals(Object obj) { } EsRelation other = (EsRelation) obj; - return Objects.equals(index, other.index) && Objects.equals(esSourceOptions, other.esSourceOptions) && frozen == other.frozen; + return Objects.equals(index, other.index) && frozen == other.frozen; } @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsqlUnresolvedRelation.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsqlUnresolvedRelation.java index 6eb5926f8b5c9..2b91ab61e43be 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsqlUnresolvedRelation.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/EsqlUnresolvedRelation.java @@ -8,54 +8,31 @@ package org.elasticsearch.xpack.esql.plan.logical; import org.elasticsearch.xpack.ql.expression.Attribute; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.plan.TableIdentifier; import org.elasticsearch.xpack.ql.tree.NodeInfo; import org.elasticsearch.xpack.ql.tree.Source; import java.util.List; -import java.util.Objects; public class EsqlUnresolvedRelation extends UnresolvedRelation { private final List metadataFields; - private final EsSourceOptions esSourceOptions; - - public EsqlUnresolvedRelation( - Source source, - TableIdentifier table, - List metadataFields, - EsSourceOptions esSourceOptions, - String unresolvedMessage - ) { - super(source, table, "", false, unresolvedMessage); - this.metadataFields = metadataFields; - Objects.requireNonNull(esSourceOptions); - this.esSourceOptions = esSourceOptions; - } public EsqlUnresolvedRelation(Source source, TableIdentifier table, List metadataFields, String unresolvedMessage) { - this(source, table, metadataFields, EsSourceOptions.NO_OPTIONS, unresolvedMessage); - } - - public EsqlUnresolvedRelation(Source source, TableIdentifier table, List metadataFields, EsSourceOptions esSourceOptions) { - this(source, table, metadataFields, esSourceOptions, null); + super(source, table, "", false, unresolvedMessage); + this.metadataFields = metadataFields; } public EsqlUnresolvedRelation(Source source, TableIdentifier table, List metadataFields) { - this(source, table, metadataFields, EsSourceOptions.NO_OPTIONS, null); + this(source, table, metadataFields, null); } public List metadataFields() { return metadataFields; } - public EsSourceOptions esSourceOptions() { - return esSourceOptions; - } - @Override protected NodeInfo info() { - return NodeInfo.create(this, EsqlUnresolvedRelation::new, table(), metadataFields(), esSourceOptions(), unresolvedMessage()); + return NodeInfo.create(this, EsqlUnresolvedRelation::new, table(), metadataFields(), unresolvedMessage()); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/PlannerUtils.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/PlannerUtils.java index fbfc57261bc40..6110ed1b72c28 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/PlannerUtils.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/PlannerUtils.java @@ -43,7 +43,6 @@ import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.expression.FieldAttribute; import org.elasticsearch.xpack.ql.expression.predicate.Predicates; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.plan.logical.Filter; import org.elasticsearch.xpack.ql.plan.logical.Limit; import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan; @@ -218,12 +217,6 @@ static QueryBuilder detectFilter(PhysicalPlan plan, String fieldName, Predicate< return Queries.combine(FILTER, asList(requestFilter)); } - public static EsSourceOptions esSourceOptions(PhysicalPlan plan) { - Holder holder = new Holder<>(); - plan.forEachUp(FragmentExec.class, f -> f.fragment().forEachUp(EsRelation.class, r -> holder.set(r.esSourceOptions()))); - return holder.get(); - } - /** * Map QL's {@link DataType} to the compute engine's {@link ElementType}, for sortable types only. * This specifically excludes spatial data types, which are not themselves sortable. diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java index d9005d5997b34..1632d8b8bf950 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/ComputeService.java @@ -72,7 +72,6 @@ import org.elasticsearch.xpack.esql.planner.LocalExecutionPlanner; import org.elasticsearch.xpack.esql.planner.PlannerUtils; import org.elasticsearch.xpack.esql.session.EsqlConfiguration; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import java.util.ArrayList; import java.util.Collections; @@ -304,51 +303,42 @@ private void startComputeOnDataNodes( // Since it's used only for @timestamp, it is relatively safe to assume it's not needed // but it would be better to have a proper impl. QueryBuilder requestFilter = PlannerUtils.requestFilter(planWithReducer, x -> true); - EsSourceOptions esSourceOptions = PlannerUtils.esSourceOptions(planWithReducer); - lookupDataNodes( - parentTask, - clusterAlias, - requestFilter, - concreteIndices, - originalIndices, - esSourceOptions, - ActionListener.wrap(dataNodes -> { - try (RefCountingRunnable refs = new RefCountingRunnable(() -> parentListener.onResponse(null))) { - // For each target node, first open a remote exchange on the remote node, then link the exchange source to - // the new remote exchange sink, and initialize the computation on the target node via data-node-request. - for (DataNode node : dataNodes) { - var dataNodeListener = ActionListener.releaseAfter(dataNodeListenerSupplier.get(), refs.acquire()); - var queryPragmas = configuration.pragmas(); - ExchangeService.openExchange( - transportService, - node.connection, - sessionId, - queryPragmas.exchangeBufferSize(), - esqlExecutor, - dataNodeListener.delegateFailureAndWrap((delegate, unused) -> { - var remoteSink = exchangeService.newRemoteSink(parentTask, sessionId, transportService, node.connection); - exchangeSource.addRemoteSink(remoteSink, queryPragmas.concurrentExchangeClients()); - transportService.sendChildRequest( - node.connection, - DATA_ACTION_NAME, - new DataNodeRequest( - sessionId, - configuration, - clusterAlias, - node.shardIds, - node.aliasFilters, - planWithReducer - ), - parentTask, - TransportRequestOptions.EMPTY, - new ActionListenerResponseHandler<>(delegate, ComputeResponse::new, esqlExecutor) - ); - }) - ); - } + lookupDataNodes(parentTask, clusterAlias, requestFilter, concreteIndices, originalIndices, ActionListener.wrap(dataNodes -> { + try (RefCountingRunnable refs = new RefCountingRunnable(() -> parentListener.onResponse(null))) { + // For each target node, first open a remote exchange on the remote node, then link the exchange source to + // the new remote exchange sink, and initialize the computation on the target node via data-node-request. + for (DataNode node : dataNodes) { + var dataNodeListener = ActionListener.releaseAfter(dataNodeListenerSupplier.get(), refs.acquire()); + var queryPragmas = configuration.pragmas(); + ExchangeService.openExchange( + transportService, + node.connection, + sessionId, + queryPragmas.exchangeBufferSize(), + esqlExecutor, + dataNodeListener.delegateFailureAndWrap((delegate, unused) -> { + var remoteSink = exchangeService.newRemoteSink(parentTask, sessionId, transportService, node.connection); + exchangeSource.addRemoteSink(remoteSink, queryPragmas.concurrentExchangeClients()); + transportService.sendChildRequest( + node.connection, + DATA_ACTION_NAME, + new DataNodeRequest( + sessionId, + configuration, + clusterAlias, + node.shardIds, + node.aliasFilters, + planWithReducer + ), + parentTask, + TransportRequestOptions.EMPTY, + new ActionListenerResponseHandler<>(delegate, ComputeResponse::new, esqlExecutor) + ); + }) + ); } - }, parentListener::onFailure) - ); + } + }, parentListener::onFailure)); } private void startComputeOnRemoteClusters( @@ -554,7 +544,6 @@ private void lookupDataNodes( QueryBuilder filter, Set concreteIndices, String[] originalIndices, - EsSourceOptions esSourceOptions, ActionListener> listener ) { ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); @@ -598,10 +587,10 @@ private void lookupDataNodes( threadContext.markAsSystemContext(); SearchShardsRequest searchShardsRequest = new SearchShardsRequest( originalIndices, - esSourceOptions.indicesOptions(SearchRequest.DEFAULT_INDICES_OPTIONS), + SearchRequest.DEFAULT_INDICES_OPTIONS, filter, null, - esSourceOptions.preference(), + null, false, clusterAlias ); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlFeatures.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlFeatures.java index cf311d4413671..cc26cff9deac7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlFeatures.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlFeatures.java @@ -111,6 +111,7 @@ public class EsqlFeatures implements FeatureSpecification { /** * Does ESQL support FROM OPTIONS? */ + @Deprecated public static final NodeFeature FROM_OPTIONS = new NodeFeature("esql.from_options"); /** diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java index ad9902a91d002..b573de7cc3435 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlIndexResolver.java @@ -11,13 +11,13 @@ import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; import org.elasticsearch.action.fieldcaps.IndexFieldCapabilities; -import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.internal.Client; import org.elasticsearch.common.Strings; import org.elasticsearch.index.mapper.TimeSeriesParams; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.ql.index.EsIndex; import org.elasticsearch.xpack.ql.index.IndexResolution; +import org.elasticsearch.xpack.ql.index.IndexResolver; import org.elasticsearch.xpack.ql.type.DataType; import org.elasticsearch.xpack.ql.type.DataTypeRegistry; import org.elasticsearch.xpack.ql.type.DateEsField; @@ -55,14 +55,9 @@ public EsqlIndexResolver(Client client, DataTypeRegistry typeRegistry) { /** * Resolves a pattern to one (potentially compound meaning that spawns multiple indices) mapping. */ - public void resolveAsMergedMapping( - String indexWildcard, - Set fieldNames, - IndicesOptions indicesOptions, - ActionListener listener - ) { + public void resolveAsMergedMapping(String indexWildcard, Set fieldNames, ActionListener listener) { client.fieldCaps( - createFieldCapsRequest(indexWildcard, fieldNames, indicesOptions), + createFieldCapsRequest(indexWildcard, fieldNames), listener.delegateFailureAndWrap((l, response) -> l.onResponse(mergedMappings(indexWildcard, response))) ); } @@ -244,13 +239,13 @@ private EsField conflictingMetricTypes(String name, String fullName, FieldCapabi return new InvalidMappedField(name, "mapped as different metric types in indices: " + indices); } - private static FieldCapabilitiesRequest createFieldCapsRequest(String index, Set fieldNames, IndicesOptions indicesOptions) { + private static FieldCapabilitiesRequest createFieldCapsRequest(String index, Set fieldNames) { FieldCapabilitiesRequest req = new FieldCapabilitiesRequest().indices(Strings.commaDelimitedListToStringArray(index)); req.fields(fieldNames.toArray(String[]::new)); req.includeUnmapped(true); // lenient because we throw our own errors looking at the response e.g. if something was not resolved // also because this way security doesn't throw authorization exceptions but rather honors ignore_unavailable - req.indicesOptions(indicesOptions); + req.indicesOptions(IndexResolver.FIELD_CAPS_INDICES_OPTIONS); req.setMergeResults(false); return req; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java index 94d559137f463..1abe994cb75c2 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java @@ -9,7 +9,6 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.fieldcaps.FieldCapabilities; -import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.common.Strings; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.core.Assertions; @@ -208,13 +207,11 @@ private void preAnalyzeIndices(LogicalPlan parsed, ActionListener void preAnalyzeIndices(LogicalPlan parsed, ActionListener fieldNames, - IndicesOptions indicesOptions, ActionListener listener ) { indexResolver.resolveAsMergedMapping(indexWildcard, fieldNames, false, Map.of(), new ActionListener<>() { @Override public void onResponse(IndexResolution fromQl) { - esqlIndexResolver.resolveAsMergedMapping(indexWildcard, fieldNames, indicesOptions, new ActionListener<>() { + esqlIndexResolver.resolveAsMergedMapping(indexWildcard, fieldNames, new ActionListener<>() { @Override public void onResponse(IndexResolution fromEsql) { if (fromQl.isValid() == false) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java new file mode 100644 index 0000000000000..3d67b4d2b1efe --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.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.expression.function.scalar.math; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; +import org.elasticsearch.xpack.ql.expression.Expression; +import org.elasticsearch.xpack.ql.tree.Source; +import org.elasticsearch.xpack.ql.type.DataTypes; +import org.elasticsearch.xpack.ql.util.NumericUtils; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.unsignedLongToDouble; + +public class CbrtTests extends AbstractFunctionTestCase { + public CbrtTests(@Name("TestCase") Supplier testCaseSupplier) { + this.testCase = testCaseSupplier.get(); + } + + @ParametersFactory + public static Iterable parameters() { + String read = "Attribute[channel=0]"; + List suppliers = new ArrayList<>(); + // Valid values + TestCaseSupplier.forUnaryInt( + suppliers, + "CbrtIntEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + Math::cbrt, + Integer.MIN_VALUE, + Integer.MAX_VALUE, + List.of() + ); + TestCaseSupplier.forUnaryLong( + suppliers, + "CbrtLongEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + Math::cbrt, + Long.MIN_VALUE, + Long.MAX_VALUE, + List.of() + ); + TestCaseSupplier.forUnaryUnsignedLong( + suppliers, + "CbrtUnsignedLongEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + ul -> Math.cbrt(unsignedLongToDouble(NumericUtils.asLongUnsigned(ul))), + BigInteger.ZERO, + UNSIGNED_LONG_MAX, + List.of() + ); + TestCaseSupplier.forUnaryDouble( + suppliers, + "CbrtDoubleEvaluator[val=" + read + "]", + DataTypes.DOUBLE, + Math::cbrt, + Double.MIN_VALUE, + Double.MAX_VALUE, + List.of() + ); + suppliers = anyNullIsNull(true, suppliers); + + return parameterSuppliersFromTypedData(errorsForCasesWithoutExamples(suppliers)); + } + + @Override + protected Expression build(Source source, List args) { + return new Cbrt(source, args.get(0)); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypesTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypesTests.java index cfa3b4a8ea6ae..8fbce3302b25f 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypesTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypesTests.java @@ -88,7 +88,6 @@ import org.elasticsearch.xpack.ql.expression.function.Function; import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.ArithmeticOperation; import org.elasticsearch.xpack.ql.index.EsIndex; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.plan.logical.Filter; import org.elasticsearch.xpack.ql.plan.logical.Limit; import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan; @@ -464,7 +463,7 @@ public void testDissectParserSimple() throws IOException { } public void testEsRelation() throws IOException { - var orig = new EsRelation(Source.EMPTY, randomEsIndex(), List.of(randomFieldAttribute()), randomEsSourceOptions(), randomBoolean()); + var orig = new EsRelation(Source.EMPTY, randomEsIndex(), List.of(randomFieldAttribute()), randomBoolean()); BytesStreamOutput bso = new BytesStreamOutput(); PlanStreamOutput out = new PlanStreamOutput(bso, planNameRegistry, null); PlanNamedTypes.writeEsRelation(out, orig); @@ -475,7 +474,7 @@ public void testEsRelation() throws IOException { public void testEsqlProject() throws IOException { var orig = new EsqlProject( Source.EMPTY, - new EsRelation(Source.EMPTY, randomEsIndex(), List.of(randomFieldAttribute()), randomEsSourceOptions(), randomBoolean()), + new EsRelation(Source.EMPTY, randomEsIndex(), List.of(randomFieldAttribute()), randomBoolean()), List.of(randomFieldAttribute()) ); BytesStreamOutput bso = new BytesStreamOutput(); @@ -486,13 +485,7 @@ public void testEsqlProject() throws IOException { } public void testMvExpand() throws IOException { - var esRelation = new EsRelation( - Source.EMPTY, - randomEsIndex(), - List.of(randomFieldAttribute()), - randomEsSourceOptions(), - randomBoolean() - ); + var esRelation = new EsRelation(Source.EMPTY, randomEsIndex(), List.of(randomFieldAttribute()), randomBoolean()); var orig = new MvExpand(Source.EMPTY, esRelation, randomFieldAttribute(), randomFieldAttribute()); BytesStreamOutput bso = new BytesStreamOutput(); PlanStreamOutput out = new PlanStreamOutput(bso, planNameRegistry, null); @@ -684,31 +677,6 @@ static Map randomProperties(int depth) { return Map.copyOf(map); } - static EsSourceOptions randomEsSourceOptions() { - EsSourceOptions eso = new EsSourceOptions(); - if (randomBoolean()) { - eso.addOption("allow_no_indices", String.valueOf(randomBoolean())); - } - if (randomBoolean()) { - eso.addOption("ignore_unavailable", String.valueOf(randomBoolean())); - } - if (randomBoolean()) { - String idsList = String.join(",", randomList(1, 5, PlanNamedTypesTests::randomName)); - eso.addOption( - "preference", - randomFrom( - "_only_local", - "_local", - "_only_nodes:" + idsList, - "_prefer_nodes:" + idsList, - "_shards:" + idsList, - randomName() - ) - ); - } - return eso; - } - static List DATA_TYPES = EsqlDataTypes.types().stream().toList(); static DataType randomDataType() { 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 ddd53cad8ec6d..633e0479b11d5 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 @@ -9,8 +9,6 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.Build; -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.common.Randomness; import org.elasticsearch.core.Tuple; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.VerificationException; @@ -594,7 +592,7 @@ public void testMetadataFieldOnOtherSources() { expectError("show info metadata _index", "line 1:11: token recognition error at: 'm'"); expectError( "explain [from foo] metadata _index", - "line 1:20: mismatched input 'metadata' expecting {'|', ',', OPENING_BRACKET, ']', 'options', 'metadata'}" + "line 1:20: mismatched input 'metadata' expecting {'|', ',', OPENING_BRACKET, ']', 'metadata'}" ); } @@ -618,106 +616,6 @@ public void testMetadataFieldNotFoundNormalField() { expectError("from test metadata emp_no", "line 1:21: unsupported metadata field [emp_no]"); } - public void testFromOptionsUnknownName() { - expectError(FROM + " options \"foo\"=\"oof\",\"bar\"=\"rab\"", "line 1:20: invalid options provided: unknown option named [foo]"); - } - - public void testFromOptionsPartialInvalid() { - expectError( - FROM + " options \"allow_no_indices\"=\"true\",\"bar\"=\"rab\"", - "line 1:46: invalid options provided: unknown option named [bar]" - ); - } - - public void testFromOptionsInvalidIndicesOptionValue() { - expectError( - FROM + " options \"allow_no_indices\"=\"foo\"", - "line 1:20: invalid options provided: Could not convert [allow_no_indices] to boolean" - ); - } - - public void testFromOptionsEmptyIndicesOptionName() { - expectError(FROM + " options \"\"=\"true\"", "line 1:20: invalid options provided: unknown option named []"); - } - - public void testFromOptionsEmptyIndicesOptionValue() { - expectError( - FROM + " options \"allow_no_indices\"=\"\"", - "line 1:20: invalid options provided: Could not convert [allow_no_indices] to boolean. " - + "Failed to parse value [] as only [true] or [false] are allowed." - ); - expectError( - FROM + " options \"ignore_unavailable\"=\"TRUE\"", - "line 1:20: invalid options provided: Could not convert [ignore_unavailable] to boolean. " - + "Failed to parse value [TRUE] as only [true] or [false] are allowed." - ); - expectError(FROM + " options \"preference\"=\"\"", "line 1:20: invalid options provided: no Preference for []"); - } - - public void testFromOptionsSuggestedOptionName() { - expectError( - FROM + " options \"allow_indices\"=\"true\"", - "line 1:20: invalid options provided: unknown option named [allow_indices], did you mean [allow_no_indices]?" - ); - } - - public void testFromOptionsInvalidPreferValue() { - expectError(FROM + " options \"preference\"=\"_foo\"", "line 1:20: invalid options provided: no Preference for [_foo]"); - } - - public void testFromOptionsUnquotedName() { - expectError(FROM + " options allow_no_indices=\"oof\"", "line 1:19: mismatched input 'allow_no_indices' expecting QUOTED_STRING"); - } - - public void testFromOptionsUnquotedValue() { - expectError(FROM + " options \"allow_no_indices\"=oof", "line 1:38: mismatched input 'oof' expecting QUOTED_STRING"); - } - - public void testFromOptionsDuplicates() { - for (var name : List.of("allow_no_indices", "ignore_unavailable", "preference")) { - String options = '"' + name + "\"=\"false\""; - options += ',' + options; - expectError(FROM + " options " + options, "invalid options provided: option [" + name + "] has already been provided"); - } - } - - public void testFromOptionsValues() { - boolean allowNoIndices = randomBoolean(); - boolean ignoreUnavailable = randomBoolean(); - String idsList = String.join(",", randomList(1, 5, () -> randomAlphaOfLengthBetween(1, 25))); - String preference = randomFrom( - "_only_local", - "_local", - "_only_nodes:" + idsList, - "_prefer_nodes:" + idsList, - "_shards:" + idsList, - randomAlphaOfLengthBetween(1, 25) - ); - List options = new ArrayList<>(3); - options.add("\"allow_no_indices\"=\"" + allowNoIndices + "\""); - options.add("\"ignore_unavailable\"=\"" + ignoreUnavailable + "\""); - options.add("\"preference\"=\"" + preference + "\""); - Randomness.shuffle(options); - String optionsList = String.join(",", options); - - var plan = statement(FROM + " OPTIONS " + optionsList); - var unresolved = as(plan, EsqlUnresolvedRelation.class); - assertNotNull(unresolved.esSourceOptions()); - var indicesOptions = unresolved.esSourceOptions().indicesOptions(SearchRequest.DEFAULT_INDICES_OPTIONS); - assertThat(indicesOptions.allowNoIndices(), is(allowNoIndices)); - assertThat(indicesOptions.ignoreUnavailable(), is(ignoreUnavailable)); - assertThat(unresolved.esSourceOptions().preference(), is(preference)); - } - - public void testFromOptionsWithMetadata() { - var plan = statement(FROM + " METADATA _id OPTIONS \"preference\"=\"foo\""); - var unresolved = as(plan, EsqlUnresolvedRelation.class); - assertNotNull(unresolved.esSourceOptions()); - assertThat(unresolved.esSourceOptions().preference(), is("foo")); - assertFalse(unresolved.metadataFields().isEmpty()); - assertThat(unresolved.metadataFields().get(0).qualifiedName(), is("_id")); - } - public void testDissectPattern() { LogicalPlan cmd = processingCommand("dissect a \"%{foo}\""); assertEquals(Dissect.class, cmd.getClass()); diff --git a/x-pack/plugin/inference/qa/mixed-cluster/build.gradle b/x-pack/plugin/inference/qa/mixed-cluster/build.gradle new file mode 100644 index 0000000000000..1d5369468b054 --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/build.gradle @@ -0,0 +1,37 @@ +import org.elasticsearch.gradle.Version +import org.elasticsearch.gradle.VersionProperties +import org.elasticsearch.gradle.util.GradleUtils +import org.elasticsearch.gradle.internal.info.BuildParams +import org.elasticsearch.gradle.testclusters.StandaloneRestIntegTestTask + +apply plugin: 'elasticsearch.internal-java-rest-test' +apply plugin: 'elasticsearch.internal-test-artifact-base' +apply plugin: 'elasticsearch.bwc-test' + +dependencies { + testImplementation project(path: ':x-pack:plugin:inference:qa:inference-service-tests') + compileOnly project(':x-pack:plugin:core') + javaRestTestImplementation(testArtifact(project(xpackModule('core')))) + javaRestTestImplementation project(path: xpackModule('inference')) + clusterPlugins project( + ':x-pack:plugin:inference:qa:test-service-plugin' + ) +} + +// inference is available in 8.11 or later +def supportedVersion = bwcVersion -> { + return bwcVersion.onOrAfter(Version.fromString("8.11.0")); +} + +BuildParams.bwcVersions.withWireCompatible(supportedVersion) { bwcVersion, baseName -> + def javaRestTest = tasks.register("v${bwcVersion}#javaRestTest", StandaloneRestIntegTestTask) { + usesBwcDistribution(bwcVersion) + systemProperty("tests.old_cluster_version", bwcVersion) + maxParallelForks = 1 + } + + tasks.register(bwcTaskName(bwcVersion)) { + dependsOn javaRestTest + } +} + diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/BaseMixedTestCase.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/BaseMixedTestCase.java new file mode 100644 index 0000000000000..2c47578f466e3 --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/BaseMixedTestCase.java @@ -0,0 +1,129 @@ +/* + * 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.qa.mixed; + +import org.apache.http.util.EntityUtils; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.http.MockWebServer; +import org.elasticsearch.test.rest.ESRestTestCase; +import org.hamcrest.Matchers; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public abstract class BaseMixedTestCase extends MixedClusterSpecTestCase { + protected static String getUrl(MockWebServer webServer) { + return Strings.format("http://%s:%s", webServer.getHostName(), webServer.getPort()); + } + + @Override + protected Settings restClientSettings() { + String token = ESRestTestCase.basicAuthHeaderValue("x_pack_rest_user", new SecureString("x-pack-test-password".toCharArray())); + return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); + } + + protected void delete(String inferenceId, TaskType taskType) throws IOException { + var request = new Request("DELETE", Strings.format("_inference/%s/%s", taskType, inferenceId)); + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + } + + protected void delete(String inferenceId) throws IOException { + var request = new Request("DELETE", Strings.format("_inference/%s", inferenceId)); + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + } + + protected Map getAll() throws IOException { + var request = new Request("GET", "_inference/_all"); + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + return ESRestTestCase.entityAsMap(response); + } + + protected Map get(String inferenceId) throws IOException { + var endpoint = Strings.format("_inference/%s", inferenceId); + var request = new Request("GET", endpoint); + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + return ESRestTestCase.entityAsMap(response); + } + + protected Map get(TaskType taskType, String inferenceId) throws IOException { + var endpoint = Strings.format("_inference/%s/%s", taskType, inferenceId); + var request = new Request("GET", endpoint); + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + return ESRestTestCase.entityAsMap(response); + } + + protected Map inference(String inferenceId, TaskType taskType, String input) throws IOException { + var endpoint = Strings.format("_inference/%s/%s", taskType, inferenceId); + var request = new Request("POST", endpoint); + request.setJsonEntity("{\"input\": [" + '"' + input + '"' + "]}"); + + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + return ESRestTestCase.entityAsMap(response); + } + + protected Map rerank(String inferenceId, List inputs, String query) throws IOException { + var endpoint = Strings.format("_inference/rerank/%s", inferenceId); + var request = new Request("POST", endpoint); + + StringBuilder body = new StringBuilder("{").append("\"query\":\"").append(query).append("\",").append("\"input\":["); + + for (int i = 0; i < inputs.size(); i++) { + body.append("\"").append(inputs.get(i)).append("\""); + if (i < inputs.size() - 1) { + body.append(","); + } + } + + body.append("]}"); + request.setJsonEntity(body.toString()); + + var response = ESRestTestCase.client().performRequest(request); + ESRestTestCase.assertOK(response); + return ESRestTestCase.entityAsMap(response); + } + + protected void put(String inferenceId, String modelConfig, TaskType taskType) throws IOException { + String endpoint = Strings.format("_inference/%s/%s?error_trace", taskType, inferenceId); + var request = new Request("PUT", endpoint); + request.setJsonEntity(modelConfig); + var response = ESRestTestCase.client().performRequest(request); + logger.warn("PUT response: {}", response.toString()); + System.out.println("PUT response: " + response.toString()); + ESRestTestCase.assertOKAndConsume(response); + } + + protected static void assertOkOrCreated(Response response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + // Once EntityUtils.toString(entity) is called the entity cannot be reused. + // Avoid that call with check here. + if (statusCode == 200 || statusCode == 201) { + return; + } + + String responseStr = EntityUtils.toString(response.getEntity()); + ESTestCase.assertThat( + responseStr, + response.getStatusLine().getStatusCode(), + Matchers.anyOf(Matchers.equalTo(200), Matchers.equalTo(201)) + ); + } +} diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java new file mode 100644 index 0000000000000..69274b46d75c1 --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/CohereServiceMixedIT.java @@ -0,0 +1,271 @@ +/* + * 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.qa.mixed; + +import org.elasticsearch.Version; +import org.elasticsearch.common.Strings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.http.MockResponse; +import org.elasticsearch.test.http.MockWebServer; +import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingType; +import org.hamcrest.Matchers; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.qa.mixed.MixedClusterSpecTestCase.bwcVersion; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.oneOf; + +public class CohereServiceMixedIT extends BaseMixedTestCase { + + private static final String COHERE_EMBEDDINGS_ADDED = "8.13.0"; + private static final String COHERE_RERANK_ADDED = "8.14.0"; + private static final String BYTE_ALIAS_FOR_INT8_ADDED = "8.14.0"; + private static final String MINIMUM_SUPPORTED_VERSION = "8.15.0"; + + private static MockWebServer cohereEmbeddingsServer; + private static MockWebServer cohereRerankServer; + + @BeforeClass + public static void startWebServer() throws IOException { + cohereEmbeddingsServer = new MockWebServer(); + cohereEmbeddingsServer.start(); + + cohereRerankServer = new MockWebServer(); + cohereRerankServer.start(); + } + + @AfterClass + public static void shutdown() { + cohereEmbeddingsServer.close(); + cohereRerankServer.close(); + } + + @SuppressWarnings("unchecked") + public void testCohereEmbeddings() throws IOException { + var embeddingsSupported = bwcVersion.onOrAfter(Version.fromString(COHERE_EMBEDDINGS_ADDED)); + assumeTrue("Cohere embedding service added in " + COHERE_EMBEDDINGS_ADDED, embeddingsSupported); + assumeTrue( + "Cohere service requires at least " + MINIMUM_SUPPORTED_VERSION, + bwcVersion.onOrAfter(Version.fromString(MINIMUM_SUPPORTED_VERSION)) + ); + + final String inferenceIdInt8 = "mixed-cluster-cohere-embeddings-int8"; + final String inferenceIdFloat = "mixed-cluster-cohere-embeddings-float"; + + // queue a response as PUT will call the service + cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseByte())); + put(inferenceIdInt8, embeddingConfigInt8(getUrl(cohereEmbeddingsServer)), TaskType.TEXT_EMBEDDING); + + // float model + cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseFloat())); + put(inferenceIdFloat, embeddingConfigFloat(getUrl(cohereEmbeddingsServer)), TaskType.TEXT_EMBEDDING); + + var configs = (List>) get(TaskType.TEXT_EMBEDDING, inferenceIdInt8).get("endpoints"); + assertEquals("cohere", configs.get(0).get("service")); + var serviceSettings = (Map) configs.get(0).get("service_settings"); + assertThat(serviceSettings, hasEntry("model_id", "embed-english-light-v3.0")); + var embeddingType = serviceSettings.get("embedding_type"); + // An upgraded node will report the embedding type as byte, an old node int8 + assertThat(embeddingType, Matchers.is(oneOf("int8", "byte"))); + + configs = (List>) get(TaskType.TEXT_EMBEDDING, inferenceIdFloat).get("endpoints"); + serviceSettings = (Map) configs.get(0).get("service_settings"); + assertThat(serviceSettings, hasEntry("embedding_type", "float")); + + assertEmbeddingInference(inferenceIdInt8, CohereEmbeddingType.BYTE); + assertEmbeddingInference(inferenceIdFloat, CohereEmbeddingType.FLOAT); + + delete(inferenceIdFloat); + delete(inferenceIdInt8); + + } + + void assertEmbeddingInference(String inferenceId, CohereEmbeddingType type) throws IOException { + switch (type) { + case INT8: + case BYTE: + cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseByte())); + break; + case FLOAT: + cohereEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponseFloat())); + } + + var inferenceMap = inference(inferenceId, TaskType.TEXT_EMBEDDING, "some text"); + assertThat(inferenceMap.entrySet(), not(empty())); + } + + @SuppressWarnings("unchecked") + public void testRerank() throws IOException { + var rerankSupported = bwcVersion.onOrAfter(Version.fromString(COHERE_RERANK_ADDED)); + assumeTrue("Cohere rerank service added in " + COHERE_RERANK_ADDED, rerankSupported); + assumeTrue( + "Cohere service requires at least " + MINIMUM_SUPPORTED_VERSION, + bwcVersion.onOrAfter(Version.fromString(MINIMUM_SUPPORTED_VERSION)) + ); + + final String inferenceId = "mixed-cluster-rerank"; + + put(inferenceId, rerankConfig(getUrl(cohereRerankServer)), TaskType.RERANK); + assertRerank(inferenceId); + + var configs = (List>) get(TaskType.RERANK, inferenceId).get("endpoints"); + assertThat(configs, hasSize(1)); + assertEquals("cohere", configs.get(0).get("service")); + var serviceSettings = (Map) configs.get(0).get("service_settings"); + assertThat(serviceSettings, hasEntry("model_id", "rerank-english-v3.0")); + var taskSettings = (Map) configs.get(0).get("task_settings"); + assertThat(taskSettings, hasEntry("top_n", 3)); + + assertRerank(inferenceId); + + } + + private void assertRerank(String inferenceId) throws IOException { + cohereRerankServer.enqueue(new MockResponse().setResponseCode(200).setBody(rerankResponse())); + var inferenceMap = rerank( + inferenceId, + List.of("luke", "like", "leia", "chewy", "r2d2", "star", "wars"), + "star wars main character" + ); + assertThat(inferenceMap.entrySet(), not(empty())); + } + + private String embeddingConfigByte(String url) { + return embeddingConfigTemplate(url, "byte"); + } + + private String embeddingConfigInt8(String url) { + return embeddingConfigTemplate(url, "int8"); + } + + private String embeddingConfigFloat(String url) { + return embeddingConfigTemplate(url, "float"); + } + + private String embeddingConfigTemplate(String url, String embeddingType) { + return Strings.format(""" + { + "service": "cohere", + "service_settings": { + "url": "%s", + "api_key": "XXXX", + "model_id": "embed-english-light-v3.0", + "embedding_type": "%s" + } + } + """, url, embeddingType); + } + + private String embeddingResponseByte() { + return """ + { + "id": "3198467e-399f-4d4a-aa2c-58af93bd6dc4", + "texts": [ + "hello" + ], + "embeddings": [ + [ + 12, + 56 + ] + ], + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 1 + } + }, + "response_type": "embeddings_bytes" + } + """; + } + + private String embeddingResponseFloat() { + return """ + { + "id": "3198467e-399f-4d4a-aa2c-58af93bd6dc4", + "texts": [ + "hello" + ], + "embeddings": [ + [ + -0.0018434525, + 0.01777649 + ] + ], + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 1 + } + }, + "response_type": "embeddings_floats" + } + """; + } + + private String rerankConfig(String url) { + return Strings.format(""" + { + "service": "cohere", + "service_settings": { + "api_key": "XXXX", + "model_id": "rerank-english-v3.0", + "url": "%s" + }, + "task_settings": { + "return_documents": false, + "top_n": 3 + } + } + """, url); + } + + private String rerankResponse() { + return """ + { + "index": "d0760819-5a73-4d58-b163-3956d3648b62", + "results": [ + { + "index": 2, + "relevance_score": 0.98005307 + }, + { + "index": 3, + "relevance_score": 0.27904198 + }, + { + "index": 0, + "relevance_score": 0.10194652 + } + ], + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "search_units": 1 + } + } + } + """; + } + +} diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/HuggingFaceServiceMixedIT.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/HuggingFaceServiceMixedIT.java new file mode 100644 index 0000000000000..a2793f9060d8a --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/HuggingFaceServiceMixedIT.java @@ -0,0 +1,147 @@ +/* + * 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.qa.mixed; + +import org.elasticsearch.Version; +import org.elasticsearch.common.Strings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.http.MockResponse; +import org.elasticsearch.test.http.MockWebServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; + +public class HuggingFaceServiceMixedIT extends BaseMixedTestCase { + + private static final String HF_EMBEDDINGS_ADDED = "8.12.0"; + private static final String HF_ELSER_ADDED = "8.12.0"; + private static final String MINIMUM_SUPPORTED_VERSION = "8.15.0"; + + private static MockWebServer embeddingsServer; + private static MockWebServer elserServer; + + @BeforeClass + public static void startWebServer() throws IOException { + embeddingsServer = new MockWebServer(); + embeddingsServer.start(); + + elserServer = new MockWebServer(); + elserServer.start(); + } + + @AfterClass + public static void shutdown() { + embeddingsServer.close(); + elserServer.close(); + } + + @SuppressWarnings("unchecked") + public void testHFEmbeddings() throws IOException { + var embeddingsSupported = bwcVersion.onOrAfter(Version.fromString(HF_EMBEDDINGS_ADDED)); + assumeTrue("Hugging Face embedding service added in " + HF_EMBEDDINGS_ADDED, embeddingsSupported); + assumeTrue( + "HuggingFace service requires at least " + MINIMUM_SUPPORTED_VERSION, + bwcVersion.onOrAfter(Version.fromString(MINIMUM_SUPPORTED_VERSION)) + ); + + final String inferenceId = "mixed-cluster-embeddings"; + + embeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponse())); + put(inferenceId, embeddingConfig(getUrl(embeddingsServer)), TaskType.TEXT_EMBEDDING); + var configs = (List>) get(TaskType.TEXT_EMBEDDING, inferenceId).get("endpoints"); + assertThat(configs, hasSize(1)); + assertEquals("hugging_face", configs.get(0).get("service")); + assertEmbeddingInference(inferenceId); + } + + void assertEmbeddingInference(String inferenceId) throws IOException { + embeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponse())); + var inferenceMap = inference(inferenceId, TaskType.TEXT_EMBEDDING, "some text"); + assertThat(inferenceMap.entrySet(), not(empty())); + } + + @SuppressWarnings("unchecked") + public void testElser() throws IOException { + var supported = bwcVersion.onOrAfter(Version.fromString(HF_ELSER_ADDED)); + assumeTrue("HF elser service added in " + HF_ELSER_ADDED, supported); + assumeTrue( + "HuggingFace service requires at least " + MINIMUM_SUPPORTED_VERSION, + bwcVersion.onOrAfter(Version.fromString(MINIMUM_SUPPORTED_VERSION)) + ); + + final String inferenceId = "mixed-cluster-elser"; + final String upgradedClusterId = "upgraded-cluster-elser"; + + put(inferenceId, elserConfig(getUrl(elserServer)), TaskType.SPARSE_EMBEDDING); + + var configs = (List>) get(TaskType.SPARSE_EMBEDDING, inferenceId).get("endpoints"); + assertThat(configs, hasSize(1)); + assertEquals("hugging_face", configs.get(0).get("service")); + assertElser(inferenceId); + } + + private void assertElser(String inferenceId) throws IOException { + elserServer.enqueue(new MockResponse().setResponseCode(200).setBody(elserResponse())); + var inferenceMap = inference(inferenceId, TaskType.SPARSE_EMBEDDING, "some text"); + assertThat(inferenceMap.entrySet(), not(empty())); + } + + private String embeddingConfig(String url) { + return Strings.format(""" + { + "service": "hugging_face", + "service_settings": { + "url": "%s", + "api_key": "XXXX" + } + } + """, url); + } + + private String embeddingResponse() { + return """ + [ + [ + 0.014539449, + -0.015288644 + ] + ] + """; + } + + private String elserConfig(String url) { + return Strings.format(""" + { + "service": "hugging_face", + "service_settings": { + "api_key": "XXXX", + "url": "%s" + } + } + """, url); + } + + private String elserResponse() { + return """ + [ + { + ".": 0.133155956864357, + "the": 0.6747211217880249 + } + ] + """; + } + +} diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/MixedClusterSpecTestCase.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/MixedClusterSpecTestCase.java new file mode 100644 index 0000000000000..45cd3716f21df --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/MixedClusterSpecTestCase.java @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.qa.mixed; + +import org.elasticsearch.Version; +import org.elasticsearch.features.NodeFeature; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.rest.ESRestTestCase; +import org.elasticsearch.test.rest.TestFeatureService; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.ClassRule; + +public abstract class MixedClusterSpecTestCase extends ESRestTestCase { + @ClassRule + public static ElasticsearchCluster cluster = MixedClustersSpec.mixedVersionCluster(); + + @Override + protected String getTestRestCluster() { + return cluster.getHttpAddresses(); + } + + static final Version bwcVersion = Version.fromString(System.getProperty("tests.old_cluster_version")); + + private static TestFeatureService oldClusterTestFeatureService = null; + + @Before + public void extractOldClusterFeatures() { + if (oldClusterTestFeatureService == null) { + oldClusterTestFeatureService = testFeatureService; + } + } + + protected static boolean oldClusterHasFeature(String featureId) { + assert oldClusterTestFeatureService != null; + return oldClusterTestFeatureService.clusterHasFeature(featureId); + } + + protected static boolean oldClusterHasFeature(NodeFeature feature) { + return oldClusterHasFeature(feature.id()); + } + + @AfterClass + public static void cleanUp() { + oldClusterTestFeatureService = null; + } + +} diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/MixedClustersSpec.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/MixedClustersSpec.java new file mode 100644 index 0000000000000..7802c2e966e01 --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/MixedClustersSpec.java @@ -0,0 +1,25 @@ +/* + * 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.qa.mixed; + +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; +import org.elasticsearch.test.cluster.util.Version; + +public class MixedClustersSpec { + public static ElasticsearchCluster mixedVersionCluster() { + Version oldVersion = Version.fromString(System.getProperty("tests.old_cluster_version")); + return ElasticsearchCluster.local() + .distribution(DistributionType.DEFAULT) + .withNode(node -> node.version(oldVersion)) + .withNode(node -> node.version(Version.CURRENT)) + .setting("xpack.security.enabled", "false") + .setting("xpack.license.self_generated.type", "trial") + .build(); + } +} diff --git a/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/OpenAIServiceMixedIT.java b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/OpenAIServiceMixedIT.java new file mode 100644 index 0000000000000..33cad6a179281 --- /dev/null +++ b/x-pack/plugin/inference/qa/mixed-cluster/src/javaRestTest/java/org/elasticsearch/xpack/inference/qa/mixed/OpenAIServiceMixedIT.java @@ -0,0 +1,223 @@ +/* + * 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.qa.mixed; + +import org.elasticsearch.Version; +import org.elasticsearch.common.Strings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.http.MockResponse; +import org.elasticsearch.test.http.MockWebServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.qa.mixed.MixedClusterSpecTestCase.bwcVersion; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; + +public class OpenAIServiceMixedIT extends BaseMixedTestCase { + + private static final String OPEN_AI_EMBEDDINGS_ADDED = "8.12.0"; + private static final String OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED = "8.13.0"; + private static final String OPEN_AI_COMPLETIONS_ADDED = "8.14.0"; + private static final String MINIMUM_SUPPORTED_VERSION = "8.15.0"; + + private static MockWebServer openAiEmbeddingsServer; + private static MockWebServer openAiChatCompletionsServer; + + @BeforeClass + public static void startWebServer() throws IOException { + openAiEmbeddingsServer = new MockWebServer(); + openAiEmbeddingsServer.start(); + + openAiChatCompletionsServer = new MockWebServer(); + openAiChatCompletionsServer.start(); + } + + @AfterClass + public static void shutdown() { + openAiEmbeddingsServer.close(); + openAiChatCompletionsServer.close(); + } + + @SuppressWarnings("unchecked") + public void testOpenAiEmbeddings() throws IOException { + var openAiEmbeddingsSupported = bwcVersion.onOrAfter(Version.fromString(OPEN_AI_EMBEDDINGS_ADDED)); + assumeTrue("OpenAI embedding service added in " + OPEN_AI_EMBEDDINGS_ADDED, openAiEmbeddingsSupported); + assumeTrue( + "OpenAI service requires at least " + MINIMUM_SUPPORTED_VERSION, + bwcVersion.onOrAfter(Version.fromString(MINIMUM_SUPPORTED_VERSION)) + ); + + final String inferenceId = "mixed-cluster-embeddings"; + + String inferenceConfig = oldClusterVersionCompatibleEmbeddingConfig(); + // queue a response as PUT will call the service + openAiEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponse())); + put(inferenceId, inferenceConfig, TaskType.TEXT_EMBEDDING); + + var configs = (List>) get(TaskType.TEXT_EMBEDDING, inferenceId).get("endpoints"); + assertThat(configs, hasSize(1)); + assertEquals("openai", configs.get(0).get("service")); + var serviceSettings = (Map) configs.get(0).get("service_settings"); + var taskSettings = (Map) configs.get(0).get("task_settings"); + var modelIdFound = serviceSettings.containsKey("model_id") || taskSettings.containsKey("model_id"); + assertTrue("model_id not found in config: " + configs.toString(), modelIdFound); + + assertEmbeddingInference(inferenceId); + } + + void assertEmbeddingInference(String inferenceId) throws IOException { + openAiEmbeddingsServer.enqueue(new MockResponse().setResponseCode(200).setBody(embeddingResponse())); + var inferenceMap = inference(inferenceId, TaskType.TEXT_EMBEDDING, "some text"); + assertThat(inferenceMap.entrySet(), not(empty())); + } + + @SuppressWarnings("unchecked") + public void testOpenAiCompletions() throws IOException { + var openAiEmbeddingsSupported = bwcVersion.onOrAfter(Version.fromString(OPEN_AI_EMBEDDINGS_ADDED)); + assumeTrue("OpenAI completions service added in " + OPEN_AI_COMPLETIONS_ADDED, openAiEmbeddingsSupported); + assumeTrue( + "OpenAI service requires at least " + MINIMUM_SUPPORTED_VERSION, + bwcVersion.onOrAfter(Version.fromString(MINIMUM_SUPPORTED_VERSION)) + ); + + final String inferenceId = "mixed-cluster-completions"; + final String upgradedClusterId = "upgraded-cluster-completions"; + + put(inferenceId, chatCompletionsConfig(getUrl(openAiChatCompletionsServer)), TaskType.COMPLETION); + + var configsMap = get(TaskType.COMPLETION, inferenceId); + logger.warn("Configs: {}", configsMap); + var configs = (List>) configsMap.get("endpoints"); + assertThat(configs, hasSize(1)); + assertEquals("openai", configs.get(0).get("service")); + var serviceSettings = (Map) configs.get(0).get("service_settings"); + assertThat(serviceSettings, hasEntry("model_id", "gpt-4")); + var taskSettings = (Map) configs.get(0).get("task_settings"); + assertThat(taskSettings.keySet(), empty()); + + assertCompletionInference(inferenceId); + } + + void assertCompletionInference(String inferenceId) throws IOException { + openAiChatCompletionsServer.enqueue(new MockResponse().setResponseCode(200).setBody(chatCompletionsResponse())); + var inferenceMap = inference(inferenceId, TaskType.COMPLETION, "some text"); + assertThat(inferenceMap.entrySet(), not(empty())); + } + + private String oldClusterVersionCompatibleEmbeddingConfig() { + if (getOldClusterTestVersion().before(OPEN_AI_EMBEDDINGS_MODEL_SETTING_MOVED)) { + return embeddingConfigWithModelInTaskSettings(getUrl(openAiEmbeddingsServer)); + } else { + return embeddingConfigWithModelInServiceSettings(getUrl(openAiEmbeddingsServer)); + } + } + + protected static org.elasticsearch.test.cluster.util.Version getOldClusterTestVersion() { + return org.elasticsearch.test.cluster.util.Version.fromString(bwcVersion.toString()); + } + + private String embeddingConfigWithModelInTaskSettings(String url) { + return Strings.format(""" + { + "service": "openai", + "service_settings": { + "api_key": "XXXX", + "url": "%s" + }, + "task_settings": { + "model": "text-embedding-ada-002" + } + } + """, url); + } + + static String embeddingConfigWithModelInServiceSettings(String url) { + return Strings.format(""" + { + "service": "openai", + "service_settings": { + "api_key": "XXXX", + "url": "%s", + "model_id": "text-embedding-ada-002" + } + } + """, url); + } + + private String chatCompletionsConfig(String url) { + return Strings.format(""" + { + "service": "openai", + "service_settings": { + "api_key": "XXXX", + "url": "%s", + "model_id": "gpt-4" + } + } + """, url); + } + + static String embeddingResponse() { + return """ + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.0123, + -0.0123 + ] + } + ], + "model": "text-embedding-ada-002", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + """; + } + + private String chatCompletionsResponse() { + return """ + { + "id": "some-id", + "object": "chat.completion", + "created": 1705397787, + "model": "gpt-3.5-turbo-0613", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "some content" + }, + "logprobs": null, + "finish_reason": "stop" + } + ], + "usage": { + "prompt_tokens": 46, + "completion_tokens": 39, + "total_tokens": 85 + }, + "system_fingerprint": null + } + """; + } + +} 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 41bef3521cdf2..4931b4da6f724 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 @@ -25,12 +25,17 @@ import org.elasticsearch.xpack.core.inference.results.SparseEmbeddingResults; import org.elasticsearch.xpack.core.inference.results.TextEmbeddingByteResults; import org.elasticsearch.xpack.core.inference.results.TextEmbeddingResults; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionServiceSettings; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionTaskSettings; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsServiceSettings; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsTaskSettings; import org.elasticsearch.xpack.inference.services.azureopenai.AzureOpenAiSecretSettings; import org.elasticsearch.xpack.inference.services.azureopenai.completion.AzureOpenAiCompletionServiceSettings; import org.elasticsearch.xpack.inference.services.azureopenai.completion.AzureOpenAiCompletionTaskSettings; import org.elasticsearch.xpack.inference.services.azureopenai.embeddings.AzureOpenAiEmbeddingsServiceSettings; import org.elasticsearch.xpack.inference.services.azureopenai.embeddings.AzureOpenAiEmbeddingsTaskSettings; import org.elasticsearch.xpack.inference.services.cohere.CohereServiceSettings; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionServiceSettings; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsServiceSettings; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsTaskSettings; import org.elasticsearch.xpack.inference.services.cohere.rerank.CohereRerankServiceSettings; @@ -69,106 +74,144 @@ public static List getNamedWriteables() { new NamedWriteableRegistry.Entry(InferenceResults.class, LegacyTextEmbeddingResults.NAME, LegacyTextEmbeddingResults::new) ); - // Inference results - namedWriteables.add( - new NamedWriteableRegistry.Entry(InferenceServiceResults.class, SparseEmbeddingResults.NAME, SparseEmbeddingResults::new) - ); - namedWriteables.add( - new NamedWriteableRegistry.Entry(InferenceServiceResults.class, TextEmbeddingResults.NAME, TextEmbeddingResults::new) - ); - namedWriteables.add( - new NamedWriteableRegistry.Entry(InferenceServiceResults.class, TextEmbeddingByteResults.NAME, TextEmbeddingByteResults::new) - ); + addInferenceResultsNamedWriteables(namedWriteables); + addChunkedInferenceResultsNamedWriteables(namedWriteables); + + // Empty default task settings + namedWriteables.add(new NamedWriteableRegistry.Entry(TaskSettings.class, EmptyTaskSettings.NAME, EmptyTaskSettings::new)); + + // Default secret settings + namedWriteables.add(new NamedWriteableRegistry.Entry(SecretSettings.class, DefaultSecretSettings.NAME, DefaultSecretSettings::new)); + + addInternalElserNamedWriteables(namedWriteables); + + // Internal TextEmbedding service config namedWriteables.add( - new NamedWriteableRegistry.Entry(InferenceServiceResults.class, ChatCompletionResults.NAME, ChatCompletionResults::new) + new NamedWriteableRegistry.Entry( + ServiceSettings.class, + ElasticsearchInternalServiceSettings.NAME, + ElasticsearchInternalServiceSettings::new + ) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(InferenceServiceResults.class, RankedDocsResults.NAME, RankedDocsResults::new) + new NamedWriteableRegistry.Entry( + ServiceSettings.class, + MultilingualE5SmallInternalServiceSettings.NAME, + MultilingualE5SmallInternalServiceSettings::new + ) ); - // Chunked inference results + addHuggingFaceNamedWriteables(namedWriteables); + addOpenAiNamedWriteables(namedWriteables); + addCohereNamedWriteables(namedWriteables); + addAzureOpenAiNamedWriteables(namedWriteables); + addAzureAiStudioNamedWriteables(namedWriteables); + + return namedWriteables; + } + + private static void addAzureAiStudioNamedWriteables(List namedWriteables) { namedWriteables.add( new NamedWriteableRegistry.Entry( - InferenceServiceResults.class, - ErrorChunkedInferenceResults.NAME, - ErrorChunkedInferenceResults::new + ServiceSettings.class, + AzureAiStudioEmbeddingsServiceSettings.NAME, + AzureAiStudioEmbeddingsServiceSettings::new ) ); namedWriteables.add( new NamedWriteableRegistry.Entry( - InferenceServiceResults.class, - ChunkedSparseEmbeddingResults.NAME, - ChunkedSparseEmbeddingResults::new + TaskSettings.class, + AzureAiStudioEmbeddingsTaskSettings.NAME, + AzureAiStudioEmbeddingsTaskSettings::new ) ); + namedWriteables.add( new NamedWriteableRegistry.Entry( - InferenceServiceResults.class, - ChunkedTextEmbeddingResults.NAME, - ChunkedTextEmbeddingResults::new + ServiceSettings.class, + AzureAiStudioChatCompletionServiceSettings.NAME, + AzureAiStudioChatCompletionServiceSettings::new ) ); namedWriteables.add( new NamedWriteableRegistry.Entry( - InferenceServiceResults.class, - ChunkedTextEmbeddingFloatResults.NAME, - ChunkedTextEmbeddingFloatResults::new + TaskSettings.class, + AzureAiStudioChatCompletionTaskSettings.NAME, + AzureAiStudioChatCompletionTaskSettings::new ) ); + } + + private static void addAzureOpenAiNamedWriteables(List namedWriteables) { namedWriteables.add( new NamedWriteableRegistry.Entry( - InferenceServiceResults.class, - ChunkedTextEmbeddingByteResults.NAME, - ChunkedTextEmbeddingByteResults::new + AzureOpenAiSecretSettings.class, + AzureOpenAiSecretSettings.NAME, + AzureOpenAiSecretSettings::new ) ); - // Empty default task settings - namedWriteables.add(new NamedWriteableRegistry.Entry(TaskSettings.class, EmptyTaskSettings.NAME, EmptyTaskSettings::new)); - - // Default secret settings - namedWriteables.add(new NamedWriteableRegistry.Entry(SecretSettings.class, DefaultSecretSettings.NAME, DefaultSecretSettings::new)); - - // Internal ELSER config namedWriteables.add( - new NamedWriteableRegistry.Entry(ServiceSettings.class, ElserInternalServiceSettings.NAME, ElserInternalServiceSettings::new) + new NamedWriteableRegistry.Entry( + ServiceSettings.class, + AzureOpenAiEmbeddingsServiceSettings.NAME, + AzureOpenAiEmbeddingsServiceSettings::new + ) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(TaskSettings.class, ElserMlNodeTaskSettings.NAME, ElserMlNodeTaskSettings::new) + new NamedWriteableRegistry.Entry( + TaskSettings.class, + AzureOpenAiEmbeddingsTaskSettings.NAME, + AzureOpenAiEmbeddingsTaskSettings::new + ) ); - // Internal TextEmbedding service config namedWriteables.add( new NamedWriteableRegistry.Entry( ServiceSettings.class, - ElasticsearchInternalServiceSettings.NAME, - ElasticsearchInternalServiceSettings::new + AzureOpenAiCompletionServiceSettings.NAME, + AzureOpenAiCompletionServiceSettings::new ) ); namedWriteables.add( new NamedWriteableRegistry.Entry( - ServiceSettings.class, - MultilingualE5SmallInternalServiceSettings.NAME, - MultilingualE5SmallInternalServiceSettings::new + TaskSettings.class, + AzureOpenAiCompletionTaskSettings.NAME, + AzureOpenAiCompletionTaskSettings::new ) ); + } - // Hugging Face config + private static void addCohereNamedWriteables(List namedWriteables) { + namedWriteables.add( + new NamedWriteableRegistry.Entry(ServiceSettings.class, CohereServiceSettings.NAME, CohereServiceSettings::new) + ); namedWriteables.add( new NamedWriteableRegistry.Entry( ServiceSettings.class, - HuggingFaceElserServiceSettings.NAME, - HuggingFaceElserServiceSettings::new + CohereEmbeddingsServiceSettings.NAME, + CohereEmbeddingsServiceSettings::new ) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(ServiceSettings.class, HuggingFaceServiceSettings.NAME, HuggingFaceServiceSettings::new) + new NamedWriteableRegistry.Entry(TaskSettings.class, CohereEmbeddingsTaskSettings.NAME, CohereEmbeddingsTaskSettings::new) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(SecretSettings.class, HuggingFaceElserSecretSettings.NAME, HuggingFaceElserSecretSettings::new) + new NamedWriteableRegistry.Entry(ServiceSettings.class, CohereRerankServiceSettings.NAME, CohereRerankServiceSettings::new) ); + namedWriteables.add( + new NamedWriteableRegistry.Entry(TaskSettings.class, CohereRerankTaskSettings.NAME, CohereRerankTaskSettings::new) + ); + namedWriteables.add( + new NamedWriteableRegistry.Entry( + ServiceSettings.class, + CohereCompletionServiceSettings.NAME, + CohereCompletionServiceSettings::new + ) + ); + } - // OpenAI + private static void addOpenAiNamedWriteables(List namedWriteables) { namedWriteables.add( new NamedWriteableRegistry.Entry( ServiceSettings.class, @@ -193,67 +236,86 @@ public static List getNamedWriteables() { OpenAiChatCompletionTaskSettings::new ) ); + } - // Cohere - namedWriteables.add( - new NamedWriteableRegistry.Entry(ServiceSettings.class, CohereServiceSettings.NAME, CohereServiceSettings::new) - ); + private static void addHuggingFaceNamedWriteables(List namedWriteables) { namedWriteables.add( new NamedWriteableRegistry.Entry( ServiceSettings.class, - CohereEmbeddingsServiceSettings.NAME, - CohereEmbeddingsServiceSettings::new + HuggingFaceElserServiceSettings.NAME, + HuggingFaceElserServiceSettings::new ) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(TaskSettings.class, CohereEmbeddingsTaskSettings.NAME, CohereEmbeddingsTaskSettings::new) + new NamedWriteableRegistry.Entry(ServiceSettings.class, HuggingFaceServiceSettings.NAME, HuggingFaceServiceSettings::new) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(ServiceSettings.class, CohereRerankServiceSettings.NAME, CohereRerankServiceSettings::new) + new NamedWriteableRegistry.Entry(SecretSettings.class, HuggingFaceElserSecretSettings.NAME, HuggingFaceElserSecretSettings::new) + ); + } + + private static void addInternalElserNamedWriteables(List namedWriteables) { + namedWriteables.add( + new NamedWriteableRegistry.Entry(ServiceSettings.class, ElserInternalServiceSettings.NAME, ElserInternalServiceSettings::new) ); namedWriteables.add( - new NamedWriteableRegistry.Entry(TaskSettings.class, CohereRerankTaskSettings.NAME, CohereRerankTaskSettings::new) + new NamedWriteableRegistry.Entry(TaskSettings.class, ElserMlNodeTaskSettings.NAME, ElserMlNodeTaskSettings::new) ); + } - // Azure OpenAI + private static void addChunkedInferenceResultsNamedWriteables(List namedWriteables) { namedWriteables.add( new NamedWriteableRegistry.Entry( - AzureOpenAiSecretSettings.class, - AzureOpenAiSecretSettings.NAME, - AzureOpenAiSecretSettings::new + InferenceServiceResults.class, + ErrorChunkedInferenceResults.NAME, + ErrorChunkedInferenceResults::new ) ); - namedWriteables.add( new NamedWriteableRegistry.Entry( - ServiceSettings.class, - AzureOpenAiEmbeddingsServiceSettings.NAME, - AzureOpenAiEmbeddingsServiceSettings::new + InferenceServiceResults.class, + ChunkedSparseEmbeddingResults.NAME, + ChunkedSparseEmbeddingResults::new ) ); namedWriteables.add( new NamedWriteableRegistry.Entry( - TaskSettings.class, - AzureOpenAiEmbeddingsTaskSettings.NAME, - AzureOpenAiEmbeddingsTaskSettings::new + InferenceServiceResults.class, + ChunkedTextEmbeddingResults.NAME, + ChunkedTextEmbeddingResults::new ) ); - namedWriteables.add( new NamedWriteableRegistry.Entry( - ServiceSettings.class, - AzureOpenAiCompletionServiceSettings.NAME, - AzureOpenAiCompletionServiceSettings::new + InferenceServiceResults.class, + ChunkedTextEmbeddingFloatResults.NAME, + ChunkedTextEmbeddingFloatResults::new ) ); namedWriteables.add( new NamedWriteableRegistry.Entry( - TaskSettings.class, - AzureOpenAiCompletionTaskSettings.NAME, - AzureOpenAiCompletionTaskSettings::new + InferenceServiceResults.class, + ChunkedTextEmbeddingByteResults.NAME, + ChunkedTextEmbeddingByteResults::new ) ); + } - return namedWriteables; + private static void addInferenceResultsNamedWriteables(List namedWriteables) { + namedWriteables.add( + new NamedWriteableRegistry.Entry(InferenceServiceResults.class, SparseEmbeddingResults.NAME, SparseEmbeddingResults::new) + ); + namedWriteables.add( + new NamedWriteableRegistry.Entry(InferenceServiceResults.class, TextEmbeddingResults.NAME, TextEmbeddingResults::new) + ); + namedWriteables.add( + new NamedWriteableRegistry.Entry(InferenceServiceResults.class, TextEmbeddingByteResults.NAME, TextEmbeddingByteResults::new) + ); + namedWriteables.add( + new NamedWriteableRegistry.Entry(InferenceServiceResults.class, ChatCompletionResults.NAME, ChatCompletionResults::new) + ); + namedWriteables.add( + new NamedWriteableRegistry.Entry(InferenceServiceResults.class, RankedDocsResults.NAME, RankedDocsResults::new) + ); } } 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 c63d6e82535c0..de2adbc471d49 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 @@ -60,6 +60,7 @@ import org.elasticsearch.xpack.inference.rest.RestInferenceAction; import org.elasticsearch.xpack.inference.rest.RestPutInferenceModelAction; import org.elasticsearch.xpack.inference.services.ServiceComponents; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioService; import org.elasticsearch.xpack.inference.services.azureopenai.AzureOpenAiService; import org.elasticsearch.xpack.inference.services.cohere.CohereService; import org.elasticsearch.xpack.inference.services.elasticsearch.ElasticsearchInternalService; @@ -188,6 +189,7 @@ public List getInferenceServiceFactories() { context -> new OpenAiService(httpFactory.get(), serviceComponents.get()), context -> new CohereService(httpFactory.get(), serviceComponents.get()), context -> new AzureOpenAiService(httpFactory.get(), serviceComponents.get()), + context -> new AzureAiStudioService(httpFactory.get(), serviceComponents.get()), ElasticsearchInternalService::new ); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioAction.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioAction.java new file mode 100644 index 0000000000000..843084312b621 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioAction.java @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.action.azureaistudio; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.http.sender.AzureAiStudioRequestManager; +import org.elasticsearch.xpack.inference.external.http.sender.InferenceInputs; +import org.elasticsearch.xpack.inference.external.http.sender.Sender; + +import static org.elasticsearch.xpack.inference.external.action.ActionUtils.createInternalServerError; +import static org.elasticsearch.xpack.inference.external.action.ActionUtils.wrapFailuresInElasticsearchException; + +public class AzureAiStudioAction implements ExecutableAction { + protected final Sender sender; + protected final AzureAiStudioRequestManager requestCreator; + protected final String errorMessage; + + protected AzureAiStudioAction(Sender sender, AzureAiStudioRequestManager requestCreator, String errorMessage) { + this.sender = sender; + this.requestCreator = requestCreator; + this.errorMessage = errorMessage; + } + + @Override + public void execute(InferenceInputs inferenceInputs, TimeValue timeout, ActionListener listener) { + try { + ActionListener wrappedListener = wrapFailuresInElasticsearchException(errorMessage, listener); + + sender.send(requestCreator, inferenceInputs, timeout, wrappedListener); + } catch (ElasticsearchException e) { + listener.onFailure(e); + } catch (Exception e) { + listener.onFailure(createInternalServerError(e, errorMessage)); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionCreator.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionCreator.java new file mode 100644 index 0000000000000..213ac22518922 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionCreator.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.inference.external.action.azureaistudio; + +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.http.sender.AzureAiStudioChatCompletionRequestManager; +import org.elasticsearch.xpack.inference.external.http.sender.AzureAiStudioEmbeddingsRequestManager; +import org.elasticsearch.xpack.inference.external.http.sender.Sender; +import org.elasticsearch.xpack.inference.services.ServiceComponents; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModel; + +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.external.action.ActionUtils.constructFailedToSendRequestMessage; + +public class AzureAiStudioActionCreator implements AzureAiStudioActionVisitor { + private final Sender sender; + private final ServiceComponents serviceComponents; + + public AzureAiStudioActionCreator(Sender sender, ServiceComponents serviceComponents) { + this.sender = Objects.requireNonNull(sender); + this.serviceComponents = Objects.requireNonNull(serviceComponents); + } + + @Override + public ExecutableAction create(AzureAiStudioChatCompletionModel completionModel, Map taskSettings) { + var overriddenModel = AzureAiStudioChatCompletionModel.of(completionModel, taskSettings); + var requestManager = new AzureAiStudioChatCompletionRequestManager(overriddenModel, serviceComponents.threadPool()); + var errorMessage = constructFailedToSendRequestMessage(completionModel.uri(), "Azure AI Studio completion"); + return new AzureAiStudioAction(sender, requestManager, errorMessage); + } + + @Override + public ExecutableAction create(AzureAiStudioEmbeddingsModel embeddingsModel, Map taskSettings) { + var overriddenModel = AzureAiStudioEmbeddingsModel.of(embeddingsModel, taskSettings); + var requestManager = new AzureAiStudioEmbeddingsRequestManager( + overriddenModel, + serviceComponents.truncator(), + serviceComponents.threadPool() + ); + var errorMessage = constructFailedToSendRequestMessage(embeddingsModel.uri(), "Azure AI Studio embeddings"); + return new AzureAiStudioAction(sender, requestManager, errorMessage); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionVisitor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionVisitor.java new file mode 100644 index 0000000000000..fee966ea2613c --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionVisitor.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.action.azureaistudio; + +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModel; + +import java.util.Map; + +public interface AzureAiStudioActionVisitor { + ExecutableAction create(AzureAiStudioEmbeddingsModel embeddingsModel, Map taskSettings); + + ExecutableAction create(AzureAiStudioChatCompletionModel completionModel, Map taskSettings); +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreator.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreator.java index 9f54950dba2d3..140c08ceef80f 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreator.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreator.java @@ -11,6 +11,7 @@ import org.elasticsearch.xpack.inference.external.action.ExecutableAction; import org.elasticsearch.xpack.inference.external.http.sender.Sender; import org.elasticsearch.xpack.inference.services.ServiceComponents; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModel; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsModel; import org.elasticsearch.xpack.inference.services.cohere.rerank.CohereRerankModel; @@ -42,4 +43,10 @@ public ExecutableAction create(CohereRerankModel model, Map task return new CohereRerankAction(sender, overriddenModel, serviceComponents.threadPool()); } + + @Override + public ExecutableAction create(CohereCompletionModel model, Map taskSettings) { + // no overridden model as task settings are always empty for cohere completion model + return new CohereCompletionAction(sender, model, serviceComponents.threadPool()); + } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionVisitor.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionVisitor.java index 5431308850f36..1d81dd9e0633b 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionVisitor.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionVisitor.java @@ -9,6 +9,7 @@ import org.elasticsearch.inference.InputType; import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModel; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsModel; import org.elasticsearch.xpack.inference.services.cohere.rerank.CohereRerankModel; @@ -18,4 +19,6 @@ public interface CohereActionVisitor { ExecutableAction create(CohereEmbeddingsModel model, Map taskSettings, InputType inputType); ExecutableAction create(CohereRerankModel model, Map taskSettings); + + ExecutableAction create(CohereCompletionModel model, Map taskSettings); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereCompletionAction.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereCompletionAction.java new file mode 100644 index 0000000000000..1df1019306699 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereCompletionAction.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.action.cohere; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.http.sender.CohereCompletionRequestManager; +import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput; +import org.elasticsearch.xpack.inference.external.http.sender.InferenceInputs; +import org.elasticsearch.xpack.inference.external.http.sender.Sender; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModel; + +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.external.action.ActionUtils.constructFailedToSendRequestMessage; +import static org.elasticsearch.xpack.inference.external.action.ActionUtils.createInternalServerError; +import static org.elasticsearch.xpack.inference.external.action.ActionUtils.wrapFailuresInElasticsearchException; + +public class CohereCompletionAction implements ExecutableAction { + + private final String failedToSendRequestErrorMessage; + + private final Sender sender; + + private final CohereCompletionRequestManager requestManager; + + public CohereCompletionAction(Sender sender, CohereCompletionModel model, ThreadPool threadPool) { + Objects.requireNonNull(model); + this.sender = Objects.requireNonNull(sender); + this.failedToSendRequestErrorMessage = constructFailedToSendRequestMessage(model.getServiceSettings().uri(), "Cohere completion"); + this.requestManager = CohereCompletionRequestManager.of(model, threadPool); + } + + @Override + public void execute(InferenceInputs inferenceInputs, TimeValue timeout, ActionListener listener) { + if (inferenceInputs instanceof DocumentsOnlyInput == false) { + listener.onFailure(new ElasticsearchStatusException("Invalid inference input type", RestStatus.INTERNAL_SERVER_ERROR)); + return; + } + + var docsOnlyInput = (DocumentsOnlyInput) inferenceInputs; + if (docsOnlyInput.getInputs().size() > 1) { + listener.onFailure(new ElasticsearchStatusException("Cohere completion only accepts 1 input", RestStatus.BAD_REQUEST)); + return; + } + + try { + ActionListener wrappedListener = wrapFailuresInElasticsearchException( + failedToSendRequestErrorMessage, + listener + ); + sender.send(requestManager, inferenceInputs, timeout, wrappedListener); + } catch (ElasticsearchException e) { + listener.onFailure(e); + } catch (Exception e) { + listener.onFailure(createInternalServerError(e, failedToSendRequestErrorMessage)); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioChatCompletionRequestManager.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioChatCompletionRequestManager.java new file mode 100644 index 0000000000000..76ef37592d88e --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioChatCompletionRequestManager.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.inference.external.http.sender; + +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.inference.external.http.retry.RequestSender; +import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler; +import org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioChatCompletionRequest; +import org.elasticsearch.xpack.inference.external.response.AzureAndOpenAiErrorResponseEntity; +import org.elasticsearch.xpack.inference.external.response.AzureAndOpenAiExternalResponseHandler; +import org.elasticsearch.xpack.inference.external.response.azureaistudio.AzureAiStudioChatCompletionResponseEntity; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModel; + +import java.util.List; +import java.util.function.Supplier; + +public class AzureAiStudioChatCompletionRequestManager extends AzureAiStudioRequestManager { + private static final Logger logger = LogManager.getLogger(AzureAiStudioChatCompletionRequestManager.class); + + private static final ResponseHandler HANDLER = createCompletionHandler(); + + private final AzureAiStudioChatCompletionModel model; + + public AzureAiStudioChatCompletionRequestManager(AzureAiStudioChatCompletionModel model, ThreadPool threadPool) { + super(threadPool, model); + this.model = model; + } + + @Override + public Runnable create( + String query, + List input, + RequestSender requestSender, + Supplier hasRequestCompletedFunction, + HttpClientContext context, + ActionListener listener + ) { + AzureAiStudioChatCompletionRequest request = new AzureAiStudioChatCompletionRequest(model, input); + + return new ExecutableInferenceRequest(requestSender, logger, request, context, HANDLER, hasRequestCompletedFunction, listener); + } + + private static ResponseHandler createCompletionHandler() { + return new AzureAndOpenAiExternalResponseHandler( + "azure ai studio completion", + new AzureAiStudioChatCompletionResponseEntity(), + AzureAndOpenAiErrorResponseEntity::fromResponse + ); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioEmbeddingsRequestManager.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioEmbeddingsRequestManager.java new file mode 100644 index 0000000000000..c2edc79dfe937 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioEmbeddingsRequestManager.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.inference.external.http.sender; + +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.inference.common.Truncator; +import org.elasticsearch.xpack.inference.external.http.retry.RequestSender; +import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler; +import org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioEmbeddingsRequest; +import org.elasticsearch.xpack.inference.external.response.AzureAndOpenAiErrorResponseEntity; +import org.elasticsearch.xpack.inference.external.response.AzureAndOpenAiExternalResponseHandler; +import org.elasticsearch.xpack.inference.external.response.azureaistudio.AzureAiStudioEmbeddingsResponseEntity; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModel; + +import java.util.List; +import java.util.function.Supplier; + +import static org.elasticsearch.xpack.inference.common.Truncator.truncate; + +public class AzureAiStudioEmbeddingsRequestManager extends AzureAiStudioRequestManager { + private static final Logger logger = LogManager.getLogger(AzureAiStudioEmbeddingsRequestManager.class); + private static final ResponseHandler HANDLER = createEmbeddingsHandler(); + + private final AzureAiStudioEmbeddingsModel model; + private final Truncator truncator; + + public AzureAiStudioEmbeddingsRequestManager(AzureAiStudioEmbeddingsModel model, Truncator truncator, ThreadPool threadPool) { + super(threadPool, model); + this.model = model; + this.truncator = truncator; + } + + @Override + public Runnable create( + String query, + List input, + RequestSender requestSender, + Supplier hasRequestCompletedFunction, + HttpClientContext context, + ActionListener listener + ) { + var truncatedInput = truncate(input, model.getServiceSettings().maxInputTokens()); + AzureAiStudioEmbeddingsRequest request = new AzureAiStudioEmbeddingsRequest(truncator, truncatedInput, model); + return new ExecutableInferenceRequest(requestSender, logger, request, context, HANDLER, hasRequestCompletedFunction, listener); + } + + private static ResponseHandler createEmbeddingsHandler() { + return new AzureAndOpenAiExternalResponseHandler( + "azure ai studio text embedding", + new AzureAiStudioEmbeddingsResponseEntity(), + AzureAndOpenAiErrorResponseEntity::fromResponse + ); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioRequestManager.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioRequestManager.java new file mode 100644 index 0000000000000..088030a22a3fb --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/AzureAiStudioRequestManager.java @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.http.sender; + +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioModel; + +import java.util.Objects; + +public abstract class AzureAiStudioRequestManager extends BaseRequestManager { + + protected AzureAiStudioRequestManager(ThreadPool threadPool, AzureAiStudioModel model) { + super(threadPool, model.getInferenceEntityId(), AzureAiStudioRequestManager.RateLimitGrouping.of(model), model.rateLimitSettings()); + } + + record RateLimitGrouping(int targetHashcode) { + public static AzureAiStudioRequestManager.RateLimitGrouping of(AzureAiStudioModel model) { + Objects.requireNonNull(model); + + return new AzureAiStudioRequestManager.RateLimitGrouping(model.target().hashCode()); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/CohereCompletionRequestManager.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/CohereCompletionRequestManager.java new file mode 100644 index 0000000000000..255d4a3f3879f --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/http/sender/CohereCompletionRequestManager.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.inference.external.http.sender; + +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.inference.external.cohere.CohereResponseHandler; +import org.elasticsearch.xpack.inference.external.http.retry.RequestSender; +import org.elasticsearch.xpack.inference.external.http.retry.ResponseHandler; +import org.elasticsearch.xpack.inference.external.request.cohere.completion.CohereCompletionRequest; +import org.elasticsearch.xpack.inference.external.response.cohere.CohereCompletionResponseEntity; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModel; + +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +public class CohereCompletionRequestManager extends CohereRequestManager { + + private static final Logger logger = LogManager.getLogger(CohereCompletionRequestManager.class); + + private static final ResponseHandler HANDLER = createCompletionHandler(); + + private static ResponseHandler createCompletionHandler() { + return new CohereResponseHandler("cohere completion", CohereCompletionResponseEntity::fromResponse); + } + + public static CohereCompletionRequestManager of(CohereCompletionModel model, ThreadPool threadPool) { + return new CohereCompletionRequestManager(Objects.requireNonNull(model), Objects.requireNonNull(threadPool)); + } + + private final CohereCompletionModel model; + + private CohereCompletionRequestManager(CohereCompletionModel model, ThreadPool threadPool) { + super(threadPool, model); + this.model = Objects.requireNonNull(model); + } + + @Override + public Runnable create( + String query, + List input, + RequestSender requestSender, + Supplier hasRequestCompletedFunction, + HttpClientContext context, + ActionListener listener + ) { + CohereCompletionRequest request = new CohereCompletionRequest(input, model); + + return new ExecutableInferenceRequest(requestSender, logger, request, context, HANDLER, hasRequestCompletedFunction, listener); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequest.java new file mode 100644 index 0000000000000..b913f79e39202 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequest.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.inference.external.request.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.elasticsearch.common.Strings; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.request.HttpRequest; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModel; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +public class AzureAiStudioChatCompletionRequest extends AzureAiStudioRequest { + private final List input; + private final AzureAiStudioChatCompletionModel completionModel; + + public AzureAiStudioChatCompletionRequest(AzureAiStudioChatCompletionModel model, List input) { + super(model); + this.input = Objects.requireNonNull(input); + this.completionModel = Objects.requireNonNull(model); + } + + public boolean isRealtimeEndpoint() { + return isRealtimeEndpoint; + } + + @Override + public HttpRequest createHttpRequest() { + HttpPost httpPost = new HttpPost(this.uri); + + ByteArrayEntity byteEntity = new ByteArrayEntity(Strings.toString(createRequestEntity()).getBytes(StandardCharsets.UTF_8)); + httpPost.setEntity(byteEntity); + + httpPost.setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType()); + setAuthHeader(httpPost, completionModel); + + return new HttpRequest(httpPost, getInferenceEntityId()); + } + + @Override + public Request truncate() { + // no truncation + return this; + } + + @Override + public boolean[] getTruncationInfo() { + // no truncation + return null; + } + + private AzureAiStudioChatCompletionRequestEntity createRequestEntity() { + var taskSettings = completionModel.getTaskSettings(); + var serviceSettings = completionModel.getServiceSettings(); + return new AzureAiStudioChatCompletionRequestEntity( + input, + serviceSettings.endpointType(), + taskSettings.temperature(), + taskSettings.topP(), + taskSettings.doSample(), + taskSettings.maxNewTokens() + ); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestEntity.java new file mode 100644 index 0000000000000..a4f685530f942 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestEntity.java @@ -0,0 +1,120 @@ +/* + * 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.azureaistudio; + +import org.elasticsearch.core.Nullable; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.INPUT_DATA_OBJECT; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.INPUT_STRING_ARRAY; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.MESSAGES_ARRAY; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.MESSAGE_CONTENT; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.PARAMETERS_OBJECT; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.ROLE; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.USER_ROLE; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.DO_SAMPLE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.MAX_NEW_TOKENS_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TEMPERATURE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TOP_P_FIELD; + +public record AzureAiStudioChatCompletionRequestEntity( + List messages, + AzureAiStudioEndpointType endpointType, + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens +) implements ToXContentObject { + + public AzureAiStudioChatCompletionRequestEntity { + Objects.requireNonNull(messages); + Objects.requireNonNull(endpointType); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + if (endpointType == AzureAiStudioEndpointType.TOKEN) { + createPayAsYouGoRequest(builder, params); + } else { + createRealtimeRequest(builder, params); + } + + builder.endObject(); + return builder; + } + + private void createRealtimeRequest(XContentBuilder builder, Params params) throws IOException { + builder.startObject(INPUT_DATA_OBJECT); + builder.startArray(INPUT_STRING_ARRAY); + + for (String message : messages) { + addMessageContentObject(builder, message); + } + + builder.endArray(); + + addRequestParameters(builder); + + builder.endObject(); + } + + private void createPayAsYouGoRequest(XContentBuilder builder, Params params) throws IOException { + builder.startArray(MESSAGES_ARRAY); + + for (String message : messages) { + addMessageContentObject(builder, message); + } + + builder.endArray(); + + addRequestParameters(builder); + } + + private void addMessageContentObject(XContentBuilder builder, String message) throws IOException { + builder.startObject(); + + builder.field(MESSAGE_CONTENT, message); + builder.field(ROLE, USER_ROLE); + + builder.endObject(); + } + + private void addRequestParameters(XContentBuilder builder) throws IOException { + if (temperature == null && topP == null && doSample == null && maxNewTokens == null) { + return; + } + + builder.startObject(PARAMETERS_OBJECT); + + if (temperature != null) { + builder.field(TEMPERATURE_FIELD, temperature); + } + + if (topP != null) { + builder.field(TOP_P_FIELD, topP); + } + + if (doSample != null) { + builder.field(DO_SAMPLE_FIELD, doSample); + } + + if (maxNewTokens != null) { + builder.field(MAX_NEW_TOKENS_FIELD, maxNewTokens); + } + + builder.endObject(); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequest.java new file mode 100644 index 0000000000000..bf828dc5789b0 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequest.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.inference.external.request.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.elasticsearch.common.Strings; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.common.Truncator; +import org.elasticsearch.xpack.inference.external.request.HttpRequest; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModel; + +import java.nio.charset.StandardCharsets; + +public class AzureAiStudioEmbeddingsRequest extends AzureAiStudioRequest { + + private final AzureAiStudioEmbeddingsModel embeddingsModel; + private final Truncator.TruncationResult truncationResult; + private final Truncator truncator; + + public AzureAiStudioEmbeddingsRequest(Truncator truncator, Truncator.TruncationResult input, AzureAiStudioEmbeddingsModel model) { + super(model); + this.embeddingsModel = model; + this.truncator = truncator; + this.truncationResult = input; + } + + @Override + public HttpRequest createHttpRequest() { + HttpPost httpPost = new HttpPost(this.uri); + + var user = embeddingsModel.getTaskSettings().user(); + var dimensions = embeddingsModel.getServiceSettings().dimensions(); + var dimensionsSetByUser = embeddingsModel.getServiceSettings().dimensionsSetByUser(); + + ByteArrayEntity byteEntity = new ByteArrayEntity( + Strings.toString(new AzureAiStudioEmbeddingsRequestEntity(truncationResult.input(), user, dimensions, dimensionsSetByUser)) + .getBytes(StandardCharsets.UTF_8) + ); + httpPost.setEntity(byteEntity); + + httpPost.setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType()); + setAuthHeader(httpPost, embeddingsModel); + + return new HttpRequest(httpPost, getInferenceEntityId()); + } + + @Override + public Request truncate() { + var truncatedInput = truncator.truncate(truncationResult.input()); + return new AzureAiStudioEmbeddingsRequest(truncator, truncatedInput, embeddingsModel); + } + + @Override + public boolean[] getTruncationInfo() { + return truncationResult.truncated().clone(); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestEntity.java new file mode 100644 index 0000000000000..a11a554b1f2e3 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestEntity.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.inference.external.request.azureaistudio; + +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; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.DIMENSIONS_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.INPUT_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.USER_FIELD; + +public record AzureAiStudioEmbeddingsRequestEntity( + List input, + @Nullable String user, + @Nullable Integer dimensions, + boolean dimensionsSetByUser +) implements ToXContentObject { + + public AzureAiStudioEmbeddingsRequestEntity { + Objects.requireNonNull(input); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + builder.field(INPUT_FIELD, input); + + if (user != null) { + builder.field(USER_FIELD, user); + } + + if (dimensionsSetByUser && dimensions != null) { + builder.field(DIMENSIONS_FIELD, dimensions); + } + + builder.endObject(); + + return builder; + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioRequest.java new file mode 100644 index 0000000000000..07daad9b89dd5 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioRequest.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.inference.external.request.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; + +import java.net.URI; + +import static org.elasticsearch.xpack.inference.external.request.RequestUtils.createAuthBearerHeader; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.API_KEY_HEADER; + +public abstract class AzureAiStudioRequest implements Request { + + protected final URI uri; + protected final String inferenceEntityId; + + protected final boolean isOpenAiRequest; + protected final boolean isRealtimeEndpoint; + + protected AzureAiStudioRequest(AzureAiStudioModel model) { + this.uri = model.uri(); + this.inferenceEntityId = model.getInferenceEntityId(); + this.isOpenAiRequest = (model.provider() == AzureAiStudioProvider.OPENAI); + this.isRealtimeEndpoint = (model.endpointType() == AzureAiStudioEndpointType.REALTIME); + } + + protected void setAuthHeader(HttpEntityEnclosingRequestBase request, AzureAiStudioModel model) { + var apiKey = model.getSecretSettings().apiKey(); + + if (isOpenAiRequest) { + request.setHeader(API_KEY_HEADER, apiKey.toString()); + } else { + if (isRealtimeEndpoint) { + request.setHeader(createAuthBearerHeader(apiKey)); + } else { + request.setHeader(HttpHeaders.AUTHORIZATION, apiKey.toString()); + } + } + } + + @Override + public URI getURI() { + return this.uri; + } + + @Override + public String getInferenceEntityId() { + return this.inferenceEntityId; + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioRequestFields.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioRequestFields.java new file mode 100644 index 0000000000000..ad10410792867 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioRequestFields.java @@ -0,0 +1,21 @@ +/* + * 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.azureaistudio; + +public final class AzureAiStudioRequestFields { + public static final String API_KEY_HEADER = "api-key"; + public static final String MESSAGES_ARRAY = "messages"; + public static final String INPUT_DATA_OBJECT = "input_data"; + public static final String INPUT_STRING_ARRAY = "input_string"; + public static final String PARAMETERS_OBJECT = "parameters"; + public static final String MESSAGE_CONTENT = "content"; + public static final String ROLE = "role"; + public static final String USER_ROLE = "user"; + + private AzureAiStudioRequestFields() {} +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereEmbeddingsRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereEmbeddingsRequest.java index 5f3278788b69b..bd59cdbded9fa 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereEmbeddingsRequest.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereEmbeddingsRequest.java @@ -7,12 +7,10 @@ package org.elasticsearch.xpack.inference.external.request.cohere; -import org.apache.http.HttpHeaders; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ByteArrayEntity; import org.elasticsearch.common.Strings; -import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.inference.external.cohere.CohereAccount; import org.elasticsearch.xpack.inference.external.request.HttpRequest; import org.elasticsearch.xpack.inference.external.request.Request; @@ -26,9 +24,7 @@ import java.util.List; import java.util.Objects; -import static org.elasticsearch.xpack.inference.external.request.RequestUtils.createAuthBearerHeader; - -public class CohereEmbeddingsRequest implements Request { +public class CohereEmbeddingsRequest extends CohereRequest { private final CohereAccount account; private final List input; @@ -57,9 +53,7 @@ public HttpRequest createHttpRequest() { ); httpPost.setEntity(byteEntity); - httpPost.setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType()); - httpPost.setHeader(createAuthBearerHeader(account.apiKey())); - httpPost.setHeader(CohereUtils.createRequestSourceHeader()); + decorateWithAuthHeader(httpPost, account); return new HttpRequest(httpPost, getInferenceEntityId()); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRequest.java new file mode 100644 index 0000000000000..17441398e33e0 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRequest.java @@ -0,0 +1,26 @@ +/* + * 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.cohere; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.cohere.CohereAccount; +import org.elasticsearch.xpack.inference.external.request.Request; + +import static org.elasticsearch.xpack.inference.external.request.RequestUtils.createAuthBearerHeader; + +public abstract class CohereRequest implements Request { + + public static void decorateWithAuthHeader(HttpPost request, CohereAccount account) { + request.setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType()); + request.setHeader(createAuthBearerHeader(account.apiKey())); + request.setHeader(CohereUtils.createRequestSourceHeader()); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRerankRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRerankRequest.java index f87bdb9ab7d4b..492807f74b32a 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRerankRequest.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRerankRequest.java @@ -7,12 +7,10 @@ package org.elasticsearch.xpack.inference.external.request.cohere; -import org.apache.http.HttpHeaders; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ByteArrayEntity; import org.elasticsearch.common.Strings; -import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.inference.external.cohere.CohereAccount; import org.elasticsearch.xpack.inference.external.request.HttpRequest; import org.elasticsearch.xpack.inference.external.request.Request; @@ -25,9 +23,7 @@ import java.util.List; import java.util.Objects; -import static org.elasticsearch.xpack.inference.external.request.RequestUtils.createAuthBearerHeader; - -public class CohereRerankRequest implements Request { +public class CohereRerankRequest extends CohereRequest { private final CohereAccount account; private final String query; @@ -56,9 +52,7 @@ public HttpRequest createHttpRequest() { ); httpPost.setEntity(byteEntity); - httpPost.setHeader(HttpHeaders.CONTENT_TYPE, XContentType.JSON.mediaType()); - httpPost.setHeader(createAuthBearerHeader(account.apiKey())); - httpPost.setHeader(CohereUtils.createRequestSourceHeader()); + decorateWithAuthHeader(httpPost, account); return new HttpRequest(httpPost, getInferenceEntityId()); } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereUtils.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereUtils.java index e6344f4d17b40..4cfba792f2c5c 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereUtils.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereUtils.java @@ -13,6 +13,7 @@ public class CohereUtils { public static final String HOST = "api.cohere.ai"; public static final String VERSION_1 = "v1"; + public static final String CHAT_PATH = "chat"; public static final String EMBEDDINGS_PATH = "embed"; public static final String RERANK_PATH = "rerank"; public static final String REQUEST_SOURCE_HEADER = "Request-Source"; diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/completion/CohereCompletionRequest.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/completion/CohereCompletionRequest.java new file mode 100644 index 0000000000000..f68f919a7d85b --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/completion/CohereCompletionRequest.java @@ -0,0 +1,88 @@ +/* + * 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.cohere.completion; + +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ByteArrayEntity; +import org.elasticsearch.common.Strings; +import org.elasticsearch.xpack.inference.external.cohere.CohereAccount; +import org.elasticsearch.xpack.inference.external.request.HttpRequest; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.external.request.cohere.CohereRequest; +import org.elasticsearch.xpack.inference.external.request.cohere.CohereUtils; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModel; + +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +public class CohereCompletionRequest extends CohereRequest { + + private final CohereAccount account; + + private final List input; + + private final String modelId; + + private final String inferenceEntityId; + + public CohereCompletionRequest(List input, CohereCompletionModel model) { + Objects.requireNonNull(model); + + this.account = CohereAccount.of(model, CohereCompletionRequest::buildDefaultUri); + this.input = Objects.requireNonNull(input); + this.modelId = model.getServiceSettings().modelId(); + this.inferenceEntityId = model.getInferenceEntityId(); + } + + @Override + public HttpRequest createHttpRequest() { + HttpPost httpPost = new HttpPost(account.uri()); + + ByteArrayEntity byteEntity = new ByteArrayEntity( + Strings.toString(new CohereCompletionRequestEntity(input, modelId)).getBytes(StandardCharsets.UTF_8) + ); + httpPost.setEntity(byteEntity); + + decorateWithAuthHeader(httpPost, account); + + return new HttpRequest(httpPost, getInferenceEntityId()); + } + + @Override + public String getInferenceEntityId() { + return inferenceEntityId; + } + + @Override + public URI getURI() { + return account.uri(); + } + + @Override + public Request truncate() { + // no truncation + return this; + } + + @Override + public boolean[] getTruncationInfo() { + // no truncation + return null; + } + + public static URI buildDefaultUri() throws URISyntaxException { + return new URIBuilder().setScheme("https") + .setHost(CohereUtils.HOST) + .setPathSegments(CohereUtils.VERSION_1, CohereUtils.CHAT_PATH) + .build(); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/completion/CohereCompletionRequestEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/completion/CohereCompletionRequestEntity.java new file mode 100644 index 0000000000000..8cb3dc6e3c8e8 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/request/cohere/completion/CohereCompletionRequestEntity.java @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.request.cohere.completion; + +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 CohereCompletionRequestEntity(List input, @Nullable String model) implements ToXContentObject { + + private static final String MESSAGE_FIELD = "message"; + + private static final String MODEL = "model"; + + public CohereCompletionRequestEntity { + Objects.requireNonNull(input); + Objects.requireNonNull(input.get(0)); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + // we only allow one input for completion, so always get the first one + builder.field(MESSAGE_FIELD, input.get(0)); + if (model != null) { + builder.field(MODEL, model); + } + + builder.endObject(); + + return builder; + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiErrorResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiErrorResponseEntity.java new file mode 100644 index 0000000000000..4ac77d6df3c33 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiErrorResponseEntity.java @@ -0,0 +1,74 @@ +/* + * 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; + +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.http.retry.ErrorMessage; + +import java.util.Map; + +/** + * A pattern is emerging in how external providers provide error responses. + * + * At a minimum, these return: + * { + * "error: { + * "message": "(error message)" + * } + * } + * + * Others may return additional information such as error codes specific to the service. + * + * This currently covers error handling for Azure AI Studio, however this pattern + * can be used to simplify and refactor handling for Azure OpenAI and OpenAI responses. + */ +public class AzureAndOpenAiErrorResponseEntity implements ErrorMessage { + protected String errorMessage; + + public AzureAndOpenAiErrorResponseEntity(String errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } + + /** + * Standard error response parser. This can be overridden for those subclasses that + * might have a different format + * + * @param response the HttpResult + * @return the error response + */ + @SuppressWarnings("unchecked") + public static ErrorMessage fromResponse(HttpResult response) { + try ( + XContentParser jsonParser = XContentFactory.xContent(XContentType.JSON) + .createParser(XContentParserConfiguration.EMPTY, response.body()) + ) { + var responseMap = jsonParser.map(); + + var error = (Map) responseMap.get("error"); + if (error != null) { + var message = (String) error.get("message"); + if (message != null) { + return new AzureAndOpenAiErrorResponseEntity(message); + } + } + } catch (Exception e) { + // swallow the error + } + + return null; + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiExternalResponseHandler.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiExternalResponseHandler.java new file mode 100644 index 0000000000000..5f803ad6fe74e --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiExternalResponseHandler.java @@ -0,0 +1,149 @@ +/* + * 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; + +import org.apache.logging.log4j.Logger; +import org.elasticsearch.common.Strings; +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.ContentTooLargeException; +import org.elasticsearch.xpack.inference.external.http.retry.ErrorMessage; +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.logging.ThrottlerManager; + +import java.util.function.Function; + +import static org.elasticsearch.xpack.inference.external.http.HttpUtils.checkForEmptyBody; +import static org.elasticsearch.xpack.inference.external.http.retry.ResponseHandlerUtils.getFirstHeaderOrUnknown; + +/** + * A base class to use for external response handling. + *

      + * This currently covers response handling for Azure AI Studio, however this pattern + * can be used to simplify and refactor handling for Azure OpenAI and OpenAI responses. + */ +public class AzureAndOpenAiExternalResponseHandler extends BaseResponseHandler { + + // The maximum number of requests that are permitted before exhausting the rate limit. + static final String REQUESTS_LIMIT = "x-ratelimit-limit-requests"; + // The maximum number of tokens that are permitted before exhausting the rate limit. + static final String TOKENS_LIMIT = "x-ratelimit-limit-tokens"; + // The remaining number of requests that are permitted before exhausting the rate limit. + static final String REMAINING_REQUESTS = "x-ratelimit-remaining-requests"; + // The remaining number of tokens that are permitted before exhausting the rate limit. + static final String REMAINING_TOKENS = "x-ratelimit-remaining-tokens"; + + static final String CONTENT_TOO_LARGE_MESSAGE = "Please reduce your prompt; or completion length."; + static final String SERVER_BUSY_ERROR = "Received a server busy error status code"; + + public AzureAndOpenAiExternalResponseHandler( + String requestType, + ResponseParser parseFunction, + Function errorParseFunction + ) { + super(requestType, parseFunction, errorParseFunction); + } + + @Override + public void validateResponse(ThrottlerManager throttlerManager, Logger logger, Request request, HttpResult result) + throws RetryException { + checkForFailureStatusCode(request, result); + checkForEmptyBody(throttlerManager, logger, request, result); + } + + public void checkForFailureStatusCode(Request request, HttpResult result) throws RetryException { + int statusCode = result.response().getStatusLine().getStatusCode(); + if (statusCode >= 200 && statusCode < 300) { + return; + } + + // handle error codes + if (statusCode == 500) { + throw handle500Error(request, result); + } else if (statusCode == 503) { + throw handle503Error(request, result); + } else if (statusCode > 500) { + throw handleOther500Error(request, result); + } else if (statusCode == 429) { + throw handleRateLimitingError(request, result); + } else if (isContentTooLarge(result)) { + throw new ContentTooLargeException(buildError(CONTENT_TOO_LARGE, request, result)); + } else if (statusCode == 401) { + throw handleAuthenticationError(request, result); + } else if (statusCode >= 300 && statusCode < 400) { + throw handleRedirectionStatusCode(request, result); + } else { + throw new RetryException(false, buildError(UNSUCCESSFUL, request, result)); + } + } + + protected RetryException handle500Error(Request request, HttpResult result) { + return new RetryException(true, buildError(SERVER_ERROR, request, result)); + } + + protected RetryException handle503Error(Request request, HttpResult result) { + return new RetryException(true, buildError(SERVER_BUSY_ERROR, request, result)); + } + + protected RetryException handleOther500Error(Request request, HttpResult result) { + return new RetryException(false, buildError(SERVER_ERROR, request, result)); + } + + protected RetryException handleAuthenticationError(Request request, HttpResult result) { + return new RetryException(false, buildError(AUTHENTICATION, request, result)); + } + + protected RetryException handleRateLimitingError(Request request, HttpResult result) { + return new RetryException(true, buildError(buildRateLimitErrorMessage(result), request, result)); + } + + protected RetryException handleRedirectionStatusCode(Request request, HttpResult result) { + throw new RetryException(false, buildError(REDIRECTION, request, result)); + } + + public static boolean isContentTooLarge(HttpResult result) { + int statusCode = result.response().getStatusLine().getStatusCode(); + + if (statusCode == 413) { + return true; + } + + if (statusCode == 400) { + var errorEntity = AzureAndOpenAiErrorResponseEntity.fromResponse(result); + return errorEntity != null && errorEntity.getErrorMessage().contains(CONTENT_TOO_LARGE_MESSAGE); + } + + return false; + } + + public static String buildRateLimitErrorMessage(HttpResult result) { + var response = result.response(); + var tokenLimit = getFirstHeaderOrUnknown(response, TOKENS_LIMIT); + var remainingTokens = getFirstHeaderOrUnknown(response, REMAINING_TOKENS); + var requestLimit = getFirstHeaderOrUnknown(response, REQUESTS_LIMIT); + var remainingRequests = getFirstHeaderOrUnknown(response, REMAINING_REQUESTS); + + if (tokenLimit.equals("unknown") && requestLimit.equals("unknown")) { + var usageMessage = Strings.format("Remaining tokens [%s]. Remaining requests [%s].", remainingTokens, remainingRequests); + return RATE_LIMIT + ". " + usageMessage; + } + + var usageMessage = Strings.format( + "Token limit [%s], remaining tokens [%s]. Request limit [%s], remaining requests [%s]", + tokenLimit, + remainingTokens, + requestLimit, + remainingRequests + ); + + return RATE_LIMIT + ". " + usageMessage; + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/BaseResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/BaseResponseEntity.java new file mode 100644 index 0000000000000..7c3c7a9645cf3 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/BaseResponseEntity.java @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.response; + +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.xpack.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.http.retry.ResponseParser; +import org.elasticsearch.xpack.inference.external.request.Request; + +import java.io.IOException; + +/** + * A base class for providing InferenceServiceResults from a response. This is a lightweight wrapper + * to be able to override the `fromReponse` method to avoid using a static reference to the method. + */ +public abstract class BaseResponseEntity implements ResponseParser { + protected abstract InferenceServiceResults fromResponse(Request request, HttpResult response) throws IOException; + + public InferenceServiceResults apply(Request request, HttpResult response) throws IOException { + return fromResponse(request, response); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioChatCompletionResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioChatCompletionResponseEntity.java new file mode 100644 index 0000000000000..18f5923353960 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioChatCompletionResponseEntity.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.external.response.azureaistudio; + +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.XContentParserUtils; +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.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioChatCompletionRequest; +import org.elasticsearch.xpack.inference.external.response.BaseResponseEntity; +import org.elasticsearch.xpack.inference.external.response.openai.OpenAiChatCompletionResponseEntity; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.elasticsearch.xpack.inference.external.response.XContentUtils.moveToFirstToken; + +public class AzureAiStudioChatCompletionResponseEntity extends BaseResponseEntity { + + @Override + protected InferenceServiceResults fromResponse(Request request, HttpResult response) throws IOException { + if (request instanceof AzureAiStudioChatCompletionRequest asChatCompletionRequest) { + if (asChatCompletionRequest.isRealtimeEndpoint()) { + return parseRealtimeEndpointResponse(response); + } + + // we can use the OpenAI chat completion type if it's not a realtime endpoint + return OpenAiChatCompletionResponseEntity.fromResponse(request, response); + } + + return null; + } + + private ChatCompletionResults parseRealtimeEndpointResponse(HttpResult response) throws IOException { + var parserConfig = XContentParserConfiguration.EMPTY.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE); + try (XContentParser jsonParser = XContentFactory.xContent(XContentType.JSON).createParser(parserConfig, response.body())) { + moveToFirstToken(jsonParser); + + XContentParser.Token token = jsonParser.currentToken(); + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, token, jsonParser); + + while (token != null && token != XContentParser.Token.END_OBJECT) { + if (token != XContentParser.Token.FIELD_NAME) { + token = jsonParser.nextToken(); + continue; + } + + var currentName = jsonParser.currentName(); + if (currentName == null || currentName.equalsIgnoreCase("output") == false) { + token = jsonParser.nextToken(); + continue; + } + + token = jsonParser.nextToken(); + ensureExpectedToken(XContentParser.Token.VALUE_STRING, token, jsonParser); + String content = jsonParser.text(); + + return new ChatCompletionResults(List.of(new ChatCompletionResults.Result(content))); + } + + throw new IllegalStateException("Reached an invalid state while parsing the Azure AI Studio completion response"); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioEmbeddingsResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioEmbeddingsResponseEntity.java new file mode 100644 index 0000000000000..3fce1ec7920f5 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioEmbeddingsResponseEntity.java @@ -0,0 +1,24 @@ +/* + * 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.azureaistudio; + +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.xpack.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.request.Request; +import org.elasticsearch.xpack.inference.external.response.BaseResponseEntity; +import org.elasticsearch.xpack.inference.external.response.openai.OpenAiEmbeddingsResponseEntity; + +import java.io.IOException; + +public class AzureAiStudioEmbeddingsResponseEntity extends BaseResponseEntity { + @Override + protected InferenceServiceResults fromResponse(Request request, HttpResult response) throws IOException { + // expected response type is the same as the Open AI Embeddings + return OpenAiEmbeddingsResponseEntity.fromResponse(request, response); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/cohere/CohereCompletionResponseEntity.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/cohere/CohereCompletionResponseEntity.java new file mode 100644 index 0000000000000..4740c93ea6c03 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/external/response/cohere/CohereCompletionResponseEntity.java @@ -0,0 +1,98 @@ +/* + * 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.cohere; + +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.XContentParserUtils; +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.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.request.Request; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.elasticsearch.xpack.inference.external.response.XContentUtils.moveToFirstToken; +import static org.elasticsearch.xpack.inference.external.response.XContentUtils.positionParserAtTokenAfterField; + +public class CohereCompletionResponseEntity { + + private static final String FAILED_TO_FIND_FIELD_TEMPLATE = "Failed to find required field [%s] in Cohere chat response"; + + /** + * Parses the Cohere chat json response. + * For a request like: + * + *

      +     *     
      +     *         {
      +     *            "message": "What is Elastic?"
      +     *         }
      +     *     
      +     * 
      + * + * The response would look like: + * + *
      +     *     
      +     *         {
      +     *              "response_id": "some id",
      +     *              "text": "response",
      +     *              "generation_id": "some id",
      +     *              "chat_history": [
      +     *                               {
      +     *                                  "role": "USER",
      +     *                                  "message": "What is Elastic?"
      +     *                               },
      +     *                               {
      +     *                                  "role": "CHATBOT",
      +     *                                  "message": "response"
      +     *                               }
      +     *               ],
      +     *              "finish_reason": "COMPLETE",
      +     *              "meta": {
      +     *                  "api_version": {
      +     *                      "version": "1"
      +     *                  },
      +     *              "billed_units": {
      +     *                      "input_tokens": 4,
      +     *                      "output_tokens": 229
      +     *                  },
      +     *              "tokens": {
      +     *                      "input_tokens": 70,
      +     *                      "output_tokens": 229
      +     *                  }
      +     *             }
      +     *          }
      +     *     
      +     * 
      + */ + + public static ChatCompletionResults fromResponse(Request request, HttpResult response) throws IOException { + var parserConfig = XContentParserConfiguration.EMPTY.withDeprecationHandler(LoggingDeprecationHandler.INSTANCE); + + try (XContentParser jsonParser = XContentFactory.xContent(XContentType.JSON).createParser(parserConfig, response.body())) { + moveToFirstToken(jsonParser); + + XContentParser.Token token = jsonParser.currentToken(); + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, token, jsonParser); + + positionParserAtTokenAfterField(jsonParser, "text", FAILED_TO_FIND_FIELD_TEMPLATE); + + XContentParser.Token contentToken = jsonParser.currentToken(); + ensureExpectedToken(XContentParser.Token.VALUE_STRING, contentToken, jsonParser); + String content = jsonParser.text(); + + return new ChatCompletionResults(List.of(new ChatCompletionResults.Result(content))); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/ServiceUtils.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/ServiceUtils.java index 47c7cc0fce015..25e8afbe1d16c 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/ServiceUtils.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/ServiceUtils.java @@ -34,6 +34,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import static org.elasticsearch.core.Strings.format; import static org.elasticsearch.xpack.inference.services.ServiceFields.SIMILARITY; @@ -91,6 +92,40 @@ public static T removeAsType(Map sourceMap, String key, Clas } } + /** + * Remove the object from the map and cast to first assignable type in the expected types list. + * If the object cannot be cast to one of the types an error is added to the + * {@code validationException} parameter + * + * @param sourceMap Map containing fields + * @param key The key of the object to remove + * @param types The expected types of the removed object + * @param validationException If the value is not of type {@code type} + * @return {@code null} if not present else the object cast to the first assignable type in the types list + */ + public static Object removeAsOneOfTypes( + Map sourceMap, + String key, + List> types, + ValidationException validationException + ) { + Object o = sourceMap.remove(key); + if (o == null) { + return null; + } + + for (Class type : types) { + if (type.isAssignableFrom(o.getClass())) { + return type.cast(o); + } + } + + validationException.addValidationError( + invalidTypesErrorMsg(key, o, types.stream().map(Class::getSimpleName).collect(Collectors.toList())) + ); + return null; + } + @SuppressWarnings("unchecked") public static Map removeFromMap(Map sourceMap, String fieldName) { return (Map) sourceMap.remove(fieldName); @@ -151,6 +186,16 @@ public static String invalidTypeErrorMsg(String settingName, Object foundObject, ); } + public static String invalidTypesErrorMsg(String settingName, Object foundObject, List expectedTypes) { + return Strings.format( + // omitting [ ] for the last string as this will be added, if you convert the list to a string anyway + "field [%s] is not of one of the expected types. The value [%s] cannot be converted to one of %s", + settingName, + foundObject, + expectedTypes + ); + } + public static String invalidUrlErrorMsg(String url, String settingName, String settingScope) { return Strings.format("[%s] Invalid url [%s] received for field [%s]", settingScope, url, settingName); } @@ -325,7 +370,7 @@ public static Integer extractOptionalPositiveInteger( } if (optionalField != null && optionalField <= 0) { - validationException.addValidationError(ServiceUtils.mustBeAPositiveNumberErrorMessage(settingName, scope, optionalField)); + validationException.addValidationError(ServiceUtils.mustBeAPositiveIntegerErrorMessage(settingName, scope, optionalField)); } if (validationException.validationErrors().size() > initialValidationErrorCount) { @@ -335,6 +380,99 @@ public static Integer extractOptionalPositiveInteger( return optionalField; } + public static Float extractOptionalFloat(Map map, String settingName) { + return ServiceUtils.removeAsType(map, settingName, Float.class); + } + + public static Double extractOptionalDoubleInRange( + Map map, + String settingName, + @Nullable Double minValue, + @Nullable Double maxValue, + String scope, + ValidationException validationException + ) { + int initialValidationErrorCount = validationException.validationErrors().size(); + var doubleReturn = ServiceUtils.removeAsType(map, settingName, Double.class, validationException); + + if (validationException.validationErrors().size() > initialValidationErrorCount) { + return null; + } + + if (doubleReturn != null && minValue != null && doubleReturn < minValue) { + validationException.addValidationError( + ServiceUtils.mustBeGreaterThanOrEqualNumberErrorMessage(settingName, scope, doubleReturn, minValue) + ); + } + + if (doubleReturn != null && maxValue != null && doubleReturn > maxValue) { + validationException.addValidationError( + ServiceUtils.mustBeLessThanOrEqualNumberErrorMessage(settingName, scope, doubleReturn, maxValue) + ); + } + + if (validationException.validationErrors().size() > initialValidationErrorCount) { + return null; + } + + return doubleReturn; + } + + public static > E extractRequiredEnum( + Map map, + String settingName, + String scope, + EnumConstructor constructor, + EnumSet validValues, + ValidationException validationException + ) { + int initialValidationErrorCount = validationException.validationErrors().size(); + var enumReturn = extractOptionalEnum(map, settingName, scope, constructor, validValues, validationException); + + if (validationException.validationErrors().size() > initialValidationErrorCount) { + return null; + } + + if (enumReturn == null) { + validationException.addValidationError(ServiceUtils.missingSettingErrorMsg(settingName, scope)); + } + + return enumReturn; + } + + public static Long extractOptionalPositiveLong( + Map map, + String settingName, + String scope, + ValidationException validationException + ) { + // We don't want callers to handle the implementation detail that a long is expected (also treat integers like a long) + List> types = List.of(Integer.class, Long.class); + int initialValidationErrorCount = validationException.validationErrors().size(); + var optionalField = ServiceUtils.removeAsOneOfTypes(map, settingName, types, validationException); + + if (optionalField != null) { + try { + // Use String.valueOf first as there's no Long.valueOf(Object o) + Long longValue = Long.valueOf(String.valueOf(optionalField)); + + if (longValue <= 0L) { + validationException.addValidationError(ServiceUtils.mustBeAPositiveLongErrorMessage(settingName, scope, longValue)); + } + + if (validationException.validationErrors().size() > initialValidationErrorCount) { + return null; + } + + return longValue; + } catch (NumberFormatException e) { + validationException.addValidationError(format("unable to parse long [%s]", e)); + } + } + + return null; + } + public static > E extractOptionalEnum( Map map, String settingName, @@ -391,10 +529,26 @@ private static > void validateEnumValue(E enumValue, EnumSet diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioConstants.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioConstants.java new file mode 100644 index 0000000000000..296b8cf09f8c0 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioConstants.java @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.azureaistudio; + +public class AzureAiStudioConstants { + public static final String EMBEDDINGS_URI_PATH = "/v1/embeddings"; + public static final String COMPLETIONS_URI_PATH = "/v1/chat/completions"; + + // common service settings fields + public static final String TARGET_FIELD = "target"; + public static final String ENDPOINT_TYPE_FIELD = "endpoint_type"; + public static final String PROVIDER_FIELD = "provider"; + public static final String API_KEY_FIELD = "api_key"; + + // embeddings service and request settings + public static final String INPUT_FIELD = "input"; + public static final String DIMENSIONS_FIELD = "dimensions"; + public static final String DIMENSIONS_SET_BY_USER = "dimensions_set_by_user"; + + // embeddings task settings fields + public static final String USER_FIELD = "user"; + + // completion task settings fields + public static final String TEMPERATURE_FIELD = "temperature"; + public static final String TOP_P_FIELD = "top_p"; + public static final String DO_SAMPLE_FIELD = "do_sample"; + public static final String MAX_TOKENS_FIELD = "max_tokens"; + public static final String MAX_NEW_TOKENS_FIELD = "max_new_tokens"; + + public static final Double MIN_TEMPERATURE_TOP_P = 0.0; + public static final Double MAX_TEMPERATURE_TOP_P = 2.0; + + private AzureAiStudioConstants() {} +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioEndpointType.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioEndpointType.java new file mode 100644 index 0000000000000..ece63f4bbf0cd --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioEndpointType.java @@ -0,0 +1,26 @@ +/* + * 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.azureaistudio; + +import java.util.Locale; + +public enum AzureAiStudioEndpointType { + TOKEN, + REALTIME; + + public static String NAME = "azure_ai_studio_endpoint_type"; + + public static AzureAiStudioEndpointType fromString(String name) { + return valueOf(name.trim().toUpperCase(Locale.ROOT)); + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioModel.java new file mode 100644 index 0000000000000..a5dd491d198ae --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioModel.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.azureaistudio; + +import org.elasticsearch.inference.Model; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.TaskSettings; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.action.azureaistudio.AzureAiStudioActionVisitor; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Objects; + +/** + * Base class for Azure AI Studio models. There are some common properties across the task types + * including: + * - target: + * - uri: + * - provider: + * - endpointType: + */ +public abstract class AzureAiStudioModel extends Model { + protected String target; + protected URI uri; + protected AzureAiStudioProvider provider; + protected AzureAiStudioEndpointType endpointType; + protected RateLimitSettings rateLimitSettings; + + public AzureAiStudioModel(AzureAiStudioModel model, TaskSettings taskSettings, RateLimitSettings rateLimitSettings) { + super(model, taskSettings); + this.rateLimitSettings = Objects.requireNonNull(rateLimitSettings); + setPropertiesFromServiceSettings((AzureAiStudioServiceSettings) model.getServiceSettings()); + } + + public AzureAiStudioModel(AzureAiStudioModel model, AzureAiStudioServiceSettings serviceSettings) { + super(model, serviceSettings); + setPropertiesFromServiceSettings(serviceSettings); + } + + protected AzureAiStudioModel(ModelConfigurations modelConfigurations, ModelSecrets modelSecrets) { + super(modelConfigurations, modelSecrets); + setPropertiesFromServiceSettings((AzureAiStudioServiceSettings) modelConfigurations.getServiceSettings()); + } + + private void setPropertiesFromServiceSettings(AzureAiStudioServiceSettings serviceSettings) { + this.target = serviceSettings.target; + this.provider = serviceSettings.provider(); + this.endpointType = serviceSettings.endpointType(); + this.rateLimitSettings = serviceSettings.rateLimitSettings(); + try { + this.uri = getEndpointUri(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + protected abstract URI getEndpointUri() throws URISyntaxException; + + public String target() { + return this.target; + } + + public RateLimitSettings rateLimitSettings() { + return this.rateLimitSettings; + } + + public AzureAiStudioProvider provider() { + return this.provider; + } + + public AzureAiStudioEndpointType endpointType() { + return this.endpointType; + } + + public URI uri() { + return this.uri; + } + + // Needed for testing only + public void setURI(String newUri) { + try { + this.uri = new URI(newUri); + } catch (URISyntaxException e) { + // swallow any error + } + } + + @Override + public DefaultSecretSettings getSecretSettings() { + return (DefaultSecretSettings) super.getSecretSettings(); + } + + public abstract ExecutableAction accept(AzureAiStudioActionVisitor creator, Map taskSettings); +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioProvider.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioProvider.java new file mode 100644 index 0000000000000..6b3efca0888f3 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioProvider.java @@ -0,0 +1,31 @@ +/* + * 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.azureaistudio; + +import java.util.Locale; + +public enum AzureAiStudioProvider { + OPENAI, + MISTRAL, + META, + MICROSOFT_PHI, + COHERE, + DATABRICKS; + + public static String NAME = "azure_ai_studio_provider"; + + public static AzureAiStudioProvider fromString(String name) { + return valueOf(name.trim().toUpperCase(Locale.ROOT)); + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioProviderCapabilities.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioProviderCapabilities.java new file mode 100644 index 0000000000000..af064707536eb --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioProviderCapabilities.java @@ -0,0 +1,85 @@ +/* + * 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.azureaistudio; + +import org.elasticsearch.inference.TaskType; + +import java.util.List; + +public final class AzureAiStudioProviderCapabilities { + + // these providers have embeddings inference + public static final List embeddingProviders = List.of( + AzureAiStudioProvider.OPENAI, + AzureAiStudioProvider.COHERE + ); + + // these providers have chat completion inference (all providers at the moment) + public static final List chatCompletionProviders = List.of(AzureAiStudioProvider.values()); + + // these providers allow token ("pay as you go") embeddings endpoints + public static final List tokenEmbeddingsProviders = List.of( + AzureAiStudioProvider.OPENAI, + AzureAiStudioProvider.COHERE + ); + + // these providers allow realtime embeddings endpoints (none at the moment) + public static final List realtimeEmbeddingsProviders = List.of(); + + // these providers allow token ("pay as you go") chat completion endpoints + public static final List tokenChatCompletionProviders = List.of( + AzureAiStudioProvider.OPENAI, + AzureAiStudioProvider.META, + AzureAiStudioProvider.COHERE + ); + + // these providers allow realtime chat completion endpoints + public static final List realtimeChatCompletionProviders = List.of( + AzureAiStudioProvider.MISTRAL, + AzureAiStudioProvider.META, + AzureAiStudioProvider.MICROSOFT_PHI, + AzureAiStudioProvider.DATABRICKS + ); + + public static boolean providerAllowsTaskType(AzureAiStudioProvider provider, TaskType taskType) { + switch (taskType) { + case COMPLETION -> { + return chatCompletionProviders.contains(provider); + } + case TEXT_EMBEDDING -> { + return embeddingProviders.contains(provider); + } + default -> { + return false; + } + } + } + + public static boolean providerAllowsEndpointTypeForTask( + AzureAiStudioProvider provider, + TaskType taskType, + AzureAiStudioEndpointType endpointType + ) { + switch (taskType) { + case COMPLETION -> { + return (endpointType == AzureAiStudioEndpointType.TOKEN) + ? tokenChatCompletionProviders.contains(provider) + : realtimeChatCompletionProviders.contains(provider); + } + case TEXT_EMBEDDING -> { + return (endpointType == AzureAiStudioEndpointType.TOKEN) + ? tokenEmbeddingsProviders.contains(provider) + : realtimeEmbeddingsProviders.contains(provider); + } + default -> { + return false; + } + } + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioService.java new file mode 100644 index 0000000000000..c488eac422401 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioService.java @@ -0,0 +1,358 @@ +/* + * 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.azureaistudio; + +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.inference.ChunkedInferenceServiceResults; +import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.inference.InputType; +import org.elasticsearch.inference.Model; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.SimilarityMeasure; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.xpack.core.inference.results.ChunkedTextEmbeddingResults; +import org.elasticsearch.xpack.core.inference.results.ErrorChunkedInferenceResults; +import org.elasticsearch.xpack.core.inference.results.TextEmbeddingResults; +import org.elasticsearch.xpack.core.ml.inference.results.ErrorInferenceResults; +import org.elasticsearch.xpack.inference.external.action.azureaistudio.AzureAiStudioActionCreator; +import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput; +import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSender; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.SenderService; +import org.elasticsearch.xpack.inference.services.ServiceComponents; +import org.elasticsearch.xpack.inference.services.ServiceUtils; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionTaskSettings; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsServiceSettings; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.elasticsearch.xpack.core.inference.results.ResultUtils.createInvalidChunkedResultException; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.createInvalidModelException; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.parsePersistedConfigErrorMsg; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.removeFromMapOrDefaultEmpty; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.removeFromMapOrThrowIfNull; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.throwIfNotEmptyMap; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProviderCapabilities.providerAllowsEndpointTypeForTask; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProviderCapabilities.providerAllowsTaskType; +import static org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionTaskSettings.DEFAULT_MAX_NEW_TOKENS; + +public class AzureAiStudioService extends SenderService { + + private static final String NAME = "azureaistudio"; + + public AzureAiStudioService(HttpRequestSender.Factory factory, ServiceComponents serviceComponents) { + super(factory, serviceComponents); + } + + @Override + protected void doInfer( + Model model, + List input, + Map taskSettings, + InputType inputType, + TimeValue timeout, + ActionListener listener + ) { + var actionCreator = new AzureAiStudioActionCreator(getSender(), getServiceComponents()); + + if (model instanceof AzureAiStudioModel baseAzureAiStudioModel) { + var action = baseAzureAiStudioModel.accept(actionCreator, taskSettings); + action.execute(new DocumentsOnlyInput(input), timeout, listener); + } else { + listener.onFailure(createInvalidModelException(model)); + } + } + + @Override + protected void doInfer( + Model model, + String query, + List input, + Map taskSettings, + InputType inputType, + TimeValue timeout, + ActionListener listener + ) { + throw new UnsupportedOperationException("Azure AI Studio service does not support inference with query input"); + } + + @Override + protected void doChunkedInfer( + Model model, + String query, + List input, + Map taskSettings, + InputType inputType, + ChunkingOptions chunkingOptions, + TimeValue timeout, + ActionListener> listener + ) { + ActionListener inferListener = listener.delegateFailureAndWrap( + (delegate, response) -> delegate.onResponse(translateToChunkedResults(input, response)) + ); + + doInfer(model, input, taskSettings, inputType, timeout, inferListener); + } + + private static List translateToChunkedResults( + List inputs, + InferenceServiceResults inferenceResults + ) { + if (inferenceResults instanceof TextEmbeddingResults textEmbeddingResults) { + return ChunkedTextEmbeddingResults.of(inputs, textEmbeddingResults); + } else if (inferenceResults instanceof ErrorInferenceResults error) { + return List.of(new ErrorChunkedInferenceResults(error.getException())); + } else { + throw createInvalidChunkedResultException(inferenceResults.getWriteableName()); + } + } + + @Override + public void parseRequestConfig( + String inferenceEntityId, + TaskType taskType, + Map config, + Set platformArchitectures, + ActionListener parsedModelListener + ) { + try { + Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); + Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + + AzureAiStudioModel model = createModel( + inferenceEntityId, + taskType, + serviceSettingsMap, + taskSettingsMap, + serviceSettingsMap, + TaskType.unsupportedTaskTypeErrorMsg(taskType, NAME), + ConfigurationParseContext.REQUEST + ); + + throwIfNotEmptyMap(config, NAME); + throwIfNotEmptyMap(serviceSettingsMap, NAME); + throwIfNotEmptyMap(taskSettingsMap, NAME); + + parsedModelListener.onResponse(model); + } catch (Exception e) { + parsedModelListener.onFailure(e); + } + } + + @Override + public AzureAiStudioModel parsePersistedConfigWithSecrets( + String inferenceEntityId, + TaskType taskType, + Map config, + Map secrets + ) { + Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); + Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + Map secretSettingsMap = removeFromMapOrDefaultEmpty(secrets, ModelSecrets.SECRET_SETTINGS); + + return createModelFromPersistent( + inferenceEntityId, + taskType, + serviceSettingsMap, + taskSettingsMap, + secretSettingsMap, + parsePersistedConfigErrorMsg(inferenceEntityId, NAME) + ); + } + + @Override + public Model parsePersistedConfig(String inferenceEntityId, TaskType taskType, Map config) { + Map serviceSettingsMap = removeFromMapOrThrowIfNull(config, ModelConfigurations.SERVICE_SETTINGS); + Map taskSettingsMap = removeFromMapOrDefaultEmpty(config, ModelConfigurations.TASK_SETTINGS); + + return createModelFromPersistent( + inferenceEntityId, + taskType, + serviceSettingsMap, + taskSettingsMap, + null, + parsePersistedConfigErrorMsg(inferenceEntityId, NAME) + ); + } + + @Override + public String name() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_AZURE_AI_STUDIO; + } + + private static AzureAiStudioModel createModel( + String inferenceEntityId, + TaskType taskType, + Map serviceSettings, + Map taskSettings, + @Nullable Map secretSettings, + String failureMessage, + ConfigurationParseContext context + ) { + + if (taskType == TaskType.TEXT_EMBEDDING) { + var embeddingsModel = new AzureAiStudioEmbeddingsModel( + inferenceEntityId, + taskType, + NAME, + serviceSettings, + taskSettings, + secretSettings, + context + ); + checkProviderAndEndpointTypeForTask( + TaskType.TEXT_EMBEDDING, + embeddingsModel.getServiceSettings().provider(), + embeddingsModel.getServiceSettings().endpointType() + ); + return embeddingsModel; + } + + if (taskType == TaskType.COMPLETION) { + var completionModel = new AzureAiStudioChatCompletionModel( + inferenceEntityId, + taskType, + NAME, + serviceSettings, + taskSettings, + secretSettings, + context + ); + checkProviderAndEndpointTypeForTask( + TaskType.COMPLETION, + completionModel.getServiceSettings().provider(), + completionModel.getServiceSettings().endpointType() + ); + return completionModel; + } + + throw new ElasticsearchStatusException(failureMessage, RestStatus.BAD_REQUEST); + } + + private AzureAiStudioModel createModelFromPersistent( + String inferenceEntityId, + TaskType taskType, + Map serviceSettings, + Map taskSettings, + Map secretSettings, + String failureMessage + ) { + return createModel( + inferenceEntityId, + taskType, + serviceSettings, + taskSettings, + secretSettings, + failureMessage, + ConfigurationParseContext.PERSISTENT + ); + } + + @Override + public void checkModelConfig(Model model, ActionListener listener) { + if (model instanceof AzureAiStudioEmbeddingsModel embeddingsModel) { + ServiceUtils.getEmbeddingSize( + model, + this, + listener.delegateFailureAndWrap((l, size) -> l.onResponse(updateEmbeddingModelConfig(embeddingsModel, size))) + ); + } else if (model instanceof AzureAiStudioChatCompletionModel chatCompletionModel) { + listener.onResponse(updateChatCompletionModelConfig(chatCompletionModel)); + } else { + listener.onResponse(model); + } + } + + private AzureAiStudioEmbeddingsModel updateEmbeddingModelConfig(AzureAiStudioEmbeddingsModel embeddingsModel, int embeddingsSize) { + if (embeddingsModel.getServiceSettings().dimensionsSetByUser() + && embeddingsModel.getServiceSettings().dimensions() != null + && embeddingsModel.getServiceSettings().dimensions() != embeddingsSize) { + throw new ElasticsearchStatusException( + Strings.format( + "The retrieved embeddings size [%s] does not match the size specified in the settings [%s]. " + + "Please recreate the [%s] configuration with the correct dimensions", + embeddingsSize, + embeddingsModel.getServiceSettings().dimensions(), + embeddingsModel.getConfigurations().getInferenceEntityId() + ), + RestStatus.BAD_REQUEST + ); + } + + var similarityFromModel = embeddingsModel.getServiceSettings().similarity(); + var similarityToUse = similarityFromModel == null ? SimilarityMeasure.DOT_PRODUCT : similarityFromModel; + + AzureAiStudioEmbeddingsServiceSettings serviceSettings = new AzureAiStudioEmbeddingsServiceSettings( + embeddingsModel.getServiceSettings().target(), + embeddingsModel.getServiceSettings().provider(), + embeddingsModel.getServiceSettings().endpointType(), + embeddingsSize, + embeddingsModel.getServiceSettings().dimensionsSetByUser(), + embeddingsModel.getServiceSettings().maxInputTokens(), + similarityToUse, + embeddingsModel.getServiceSettings().rateLimitSettings() + ); + + return new AzureAiStudioEmbeddingsModel(embeddingsModel, serviceSettings); + } + + private AzureAiStudioChatCompletionModel updateChatCompletionModelConfig(AzureAiStudioChatCompletionModel chatCompletionModel) { + var modelMaxNewTokens = chatCompletionModel.getTaskSettings().maxNewTokens(); + var maxNewTokensToUse = modelMaxNewTokens == null ? DEFAULT_MAX_NEW_TOKENS : modelMaxNewTokens; + var updatedTaskSettings = new AzureAiStudioChatCompletionTaskSettings( + chatCompletionModel.getTaskSettings().temperature(), + chatCompletionModel.getTaskSettings().topP(), + chatCompletionModel.getTaskSettings().doSample(), + maxNewTokensToUse + ); + return new AzureAiStudioChatCompletionModel(chatCompletionModel, updatedTaskSettings); + } + + private static void checkProviderAndEndpointTypeForTask( + TaskType taskType, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType + ) { + if (providerAllowsTaskType(provider, taskType) == false) { + throw new ElasticsearchStatusException( + Strings.format("The [%s] task type for provider [%s] is not available", taskType, provider), + RestStatus.BAD_REQUEST + ); + } + + if (providerAllowsEndpointTypeForTask(provider, taskType, endpointType) == false) { + throw new ElasticsearchStatusException( + Strings.format( + "The [%s] endpoint type with [%s] task type for provider [%s] is not available", + endpointType, + taskType, + provider + ), + RestStatus.BAD_REQUEST + ); + } + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioServiceSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioServiceSettings.java new file mode 100644 index 0000000000000..10c57e19b6403 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioServiceSettings.java @@ -0,0 +1,130 @@ +/* + * 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.azureaistudio; + +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.Nullable; +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.settings.FilteredXContentObject; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractRequiredEnum; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractRequiredString; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.ENDPOINT_TYPE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.PROVIDER_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TARGET_FIELD; + +public abstract class AzureAiStudioServiceSettings extends FilteredXContentObject implements ServiceSettings { + + protected final String target; + protected final AzureAiStudioProvider provider; + protected final AzureAiStudioEndpointType endpointType; + protected final RateLimitSettings rateLimitSettings; + + protected static final RateLimitSettings DEFAULT_RATE_LIMIT_SETTINGS = new RateLimitSettings(240); + + protected static BaseAzureAiStudioCommonFields fromMap( + Map map, + ValidationException validationException, + ConfigurationParseContext context + ) { + String target = extractRequiredString(map, TARGET_FIELD, ModelConfigurations.SERVICE_SETTINGS, validationException); + RateLimitSettings rateLimitSettings = RateLimitSettings.of(map, DEFAULT_RATE_LIMIT_SETTINGS, validationException); + AzureAiStudioEndpointType endpointType = extractRequiredEnum( + map, + ENDPOINT_TYPE_FIELD, + ModelConfigurations.SERVICE_SETTINGS, + AzureAiStudioEndpointType::fromString, + EnumSet.allOf(AzureAiStudioEndpointType.class), + validationException + ); + + AzureAiStudioProvider provider = extractRequiredEnum( + map, + PROVIDER_FIELD, + ModelConfigurations.SERVICE_SETTINGS, + AzureAiStudioProvider::fromString, + EnumSet.allOf(AzureAiStudioProvider.class), + validationException + ); + + return new BaseAzureAiStudioCommonFields(target, provider, endpointType, rateLimitSettings); + } + + protected AzureAiStudioServiceSettings(StreamInput in) throws IOException { + this.target = in.readString(); + this.provider = in.readEnum(AzureAiStudioProvider.class); + this.endpointType = in.readEnum(AzureAiStudioEndpointType.class); + this.rateLimitSettings = new RateLimitSettings(in); + } + + protected AzureAiStudioServiceSettings( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + @Nullable RateLimitSettings rateLimitSettings + ) { + this.target = target; + this.provider = provider; + this.endpointType = endpointType; + this.rateLimitSettings = Objects.requireNonNullElse(rateLimitSettings, DEFAULT_RATE_LIMIT_SETTINGS); + } + + protected record BaseAzureAiStudioCommonFields( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + RateLimitSettings rateLimitSettings + ) {} + + public String target() { + return this.target; + } + + public AzureAiStudioProvider provider() { + return this.provider; + } + + public AzureAiStudioEndpointType endpointType() { + return this.endpointType; + } + + public RateLimitSettings rateLimitSettings() { + return this.rateLimitSettings; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(target); + out.writeEnum(provider); + out.writeEnum(endpointType); + rateLimitSettings.writeTo(out); + } + + protected void addXContentFields(XContentBuilder builder, Params params) throws IOException { + this.addExposedXContentFields(builder, params); + rateLimitSettings.toXContent(builder, params); + } + + protected void addExposedXContentFields(XContentBuilder builder, Params params) throws IOException { + builder.field(TARGET_FIELD, this.target); + builder.field(PROVIDER_FIELD, this.provider); + builder.field(ENDPOINT_TYPE_FIELD, this.endpointType); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionModel.java new file mode 100644 index 0000000000000..5afb3aaed61ff --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionModel.java @@ -0,0 +1,105 @@ +/* + * 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.azureaistudio.completion; + +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.action.azureaistudio.AzureAiStudioActionVisitor; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.COMPLETIONS_URI_PATH; + +public class AzureAiStudioChatCompletionModel extends AzureAiStudioModel { + + public static AzureAiStudioChatCompletionModel of(AzureAiStudioModel model, Map taskSettings) { + var modelAsCompletionModel = (AzureAiStudioChatCompletionModel) model; + + if (taskSettings == null || taskSettings.isEmpty()) { + return modelAsCompletionModel; + } + + var requestTaskSettings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(taskSettings); + var taskSettingToUse = AzureAiStudioChatCompletionTaskSettings.of(modelAsCompletionModel.getTaskSettings(), requestTaskSettings); + + return new AzureAiStudioChatCompletionModel(modelAsCompletionModel, taskSettingToUse); + } + + public AzureAiStudioChatCompletionModel( + String inferenceEntityId, + TaskType taskType, + String service, + AzureAiStudioChatCompletionServiceSettings serviceSettings, + AzureAiStudioChatCompletionTaskSettings taskSettings, + DefaultSecretSettings secrets + ) { + super(new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, taskSettings), new ModelSecrets(secrets)); + } + + public AzureAiStudioChatCompletionModel( + String inferenceEntityId, + TaskType taskType, + String service, + Map serviceSettings, + Map taskSettings, + @Nullable Map secrets, + ConfigurationParseContext context + ) { + this( + inferenceEntityId, + taskType, + service, + AzureAiStudioChatCompletionServiceSettings.fromMap(serviceSettings, context), + AzureAiStudioChatCompletionTaskSettings.fromMap(taskSettings), + DefaultSecretSettings.fromMap(secrets) + ); + } + + public AzureAiStudioChatCompletionModel(AzureAiStudioChatCompletionModel model, AzureAiStudioChatCompletionTaskSettings taskSettings) { + super(model, taskSettings, model.getServiceSettings().rateLimitSettings()); + } + + @Override + public AzureAiStudioChatCompletionServiceSettings getServiceSettings() { + return (AzureAiStudioChatCompletionServiceSettings) super.getServiceSettings(); + } + + @Override + public AzureAiStudioChatCompletionTaskSettings getTaskSettings() { + return (AzureAiStudioChatCompletionTaskSettings) super.getTaskSettings(); + } + + @Override + public DefaultSecretSettings getSecretSettings() { + return super.getSecretSettings(); + } + + @Override + protected URI getEndpointUri() throws URISyntaxException { + if (this.provider == AzureAiStudioProvider.OPENAI || this.endpointType == AzureAiStudioEndpointType.REALTIME) { + return new URI(this.target); + } + + return new URI(this.target + COMPLETIONS_URI_PATH); + } + + @Override + public ExecutableAction accept(AzureAiStudioActionVisitor creator, Map taskSettings) { + return creator.create(this, taskSettings); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionRequestTaskSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionRequestTaskSettings.java new file mode 100644 index 0000000000000..2eef059e3fae1 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionRequestTaskSettings.java @@ -0,0 +1,83 @@ +/* + * 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.azureaistudio.completion; + +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants; + +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalBoolean; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalDoubleInRange; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalPositiveInteger; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.DO_SAMPLE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.MAX_NEW_TOKENS_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TEMPERATURE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TOP_P_FIELD; + +public record AzureAiStudioChatCompletionRequestTaskSettings( + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens +) { + + public static final AzureAiStudioChatCompletionRequestTaskSettings EMPTY_SETTINGS = new AzureAiStudioChatCompletionRequestTaskSettings( + null, + null, + null, + null + ); + + /** + * Extracts the task settings from a map. All settings are considered optional and the absence of a setting + * does not throw an error. + * + * @param map the settings received from a request + * @return a {@link AzureAiStudioChatCompletionRequestTaskSettings} + */ + public static AzureAiStudioChatCompletionRequestTaskSettings fromMap(Map map) { + if (map.isEmpty()) { + return AzureAiStudioChatCompletionRequestTaskSettings.EMPTY_SETTINGS; + } + + ValidationException validationException = new ValidationException(); + + var temperature = extractOptionalDoubleInRange( + map, + TEMPERATURE_FIELD, + AzureAiStudioConstants.MIN_TEMPERATURE_TOP_P, + AzureAiStudioConstants.MAX_TEMPERATURE_TOP_P, + ModelConfigurations.TASK_SETTINGS, + validationException + ); + var topP = extractOptionalDoubleInRange( + map, + TOP_P_FIELD, + AzureAiStudioConstants.MIN_TEMPERATURE_TOP_P, + AzureAiStudioConstants.MAX_TEMPERATURE_TOP_P, + ModelConfigurations.TASK_SETTINGS, + validationException + ); + Boolean doSample = extractOptionalBoolean(map, DO_SAMPLE_FIELD, validationException); + Integer maxNewTokens = extractOptionalPositiveInteger( + map, + MAX_NEW_TOKENS_FIELD, + ModelConfigurations.TASK_SETTINGS, + validationException + ); + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new AzureAiStudioChatCompletionRequestTaskSettings(temperature, topP, doSample, maxNewTokens); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionServiceSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionServiceSettings.java new file mode 100644 index 0000000000000..2f8422be5ed90 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionServiceSettings.java @@ -0,0 +1,123 @@ +/* + * 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.azureaistudio.completion; + +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.core.Nullable; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioServiceSettings; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +public class AzureAiStudioChatCompletionServiceSettings extends AzureAiStudioServiceSettings { + public static final String NAME = "azure_ai_studio_chat_completion_service_settings"; + + public static AzureAiStudioChatCompletionServiceSettings fromMap(Map map, ConfigurationParseContext context) { + ValidationException validationException = new ValidationException(); + + var settings = completionSettingsFromMap(map, validationException, context); + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new AzureAiStudioChatCompletionServiceSettings(settings); + } + + private static AzureAiStudioCompletionCommonFields completionSettingsFromMap( + Map map, + ValidationException validationException, + ConfigurationParseContext context + ) { + var baseSettings = AzureAiStudioServiceSettings.fromMap(map, validationException, context); + return new AzureAiStudioCompletionCommonFields(baseSettings); + } + + private record AzureAiStudioCompletionCommonFields(BaseAzureAiStudioCommonFields baseCommonFields) {} + + public AzureAiStudioChatCompletionServiceSettings( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + @Nullable RateLimitSettings rateLimitSettings + ) { + super(target, provider, endpointType, rateLimitSettings); + } + + public AzureAiStudioChatCompletionServiceSettings(StreamInput in) throws IOException { + super(in); + } + + private AzureAiStudioChatCompletionServiceSettings(AzureAiStudioCompletionCommonFields fields) { + this( + fields.baseCommonFields.target(), + fields.baseCommonFields.provider(), + fields.baseCommonFields.endpointType(), + fields.baseCommonFields.rateLimitSettings() + ); + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_AZURE_AI_STUDIO; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + super.addXContentFields(builder, params); + + builder.endObject(); + return builder; + } + + @Override + protected XContentBuilder toXContentFragmentOfExposedFields(XContentBuilder builder, ToXContent.Params params) throws IOException { + super.addExposedXContentFields(builder, params); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AzureAiStudioChatCompletionServiceSettings that = (AzureAiStudioChatCompletionServiceSettings) o; + + return Objects.equals(target, that.target) + && Objects.equals(provider, that.provider) + && Objects.equals(endpointType, that.endpointType) + && Objects.equals(rateLimitSettings, that.rateLimitSettings); + } + + @Override + public int hashCode() { + return Objects.hash(target, provider, endpointType, rateLimitSettings); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionTaskSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionTaskSettings.java new file mode 100644 index 0000000000000..fc11d96269b68 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionTaskSettings.java @@ -0,0 +1,192 @@ +/* + * 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.azureaistudio.completion; + +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.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.TaskSettings; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants; +import org.elasticsearch.xpack.inference.services.azureopenai.embeddings.AzureOpenAiEmbeddingsTaskSettings; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalBoolean; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalDoubleInRange; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalPositiveInteger; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.DO_SAMPLE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.MAX_NEW_TOKENS_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TEMPERATURE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TOP_P_FIELD; + +public class AzureAiStudioChatCompletionTaskSettings implements TaskSettings { + public static final String NAME = "azure_ai_studio_chat_completion_task_settings"; + public static final Integer DEFAULT_MAX_NEW_TOKENS = 64; + + public static AzureAiStudioChatCompletionTaskSettings fromMap(Map map) { + ValidationException validationException = new ValidationException(); + + var temperature = extractOptionalDoubleInRange( + map, + TEMPERATURE_FIELD, + AzureAiStudioConstants.MIN_TEMPERATURE_TOP_P, + AzureAiStudioConstants.MAX_TEMPERATURE_TOP_P, + ModelConfigurations.TASK_SETTINGS, + validationException + ); + var topP = extractOptionalDoubleInRange( + map, + TOP_P_FIELD, + AzureAiStudioConstants.MIN_TEMPERATURE_TOP_P, + AzureAiStudioConstants.MAX_TEMPERATURE_TOP_P, + ModelConfigurations.TASK_SETTINGS, + validationException + ); + var doSample = extractOptionalBoolean(map, DO_SAMPLE_FIELD, validationException); + var maxNewTokens = extractOptionalPositiveInteger( + map, + MAX_NEW_TOKENS_FIELD, + ModelConfigurations.TASK_SETTINGS, + validationException + ); + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new AzureAiStudioChatCompletionTaskSettings(temperature, topP, doSample, maxNewTokens); + } + + /** + * Creates a new {@link AzureOpenAiEmbeddingsTaskSettings} object by overriding the values in originalSettings with the ones + * passed in via requestSettings if the fields are not null. + * @param originalSettings the original {@link AzureOpenAiEmbeddingsTaskSettings} from the inference entity configuration from storage + * @param requestSettings the {@link AzureOpenAiEmbeddingsTaskSettings} from the request + * @return a new {@link AzureOpenAiEmbeddingsTaskSettings} + */ + public static AzureAiStudioChatCompletionTaskSettings of( + AzureAiStudioChatCompletionTaskSettings originalSettings, + AzureAiStudioChatCompletionRequestTaskSettings requestSettings + ) { + + var temperature = requestSettings.temperature() == null ? originalSettings.temperature() : requestSettings.temperature(); + var topP = requestSettings.topP() == null ? originalSettings.topP() : requestSettings.topP(); + var doSample = requestSettings.doSample() == null ? originalSettings.doSample() : requestSettings.doSample(); + var maxNewTokens = requestSettings.maxNewTokens() == null ? originalSettings.maxNewTokens() : requestSettings.maxNewTokens(); + + return new AzureAiStudioChatCompletionTaskSettings(temperature, topP, doSample, maxNewTokens); + } + + public AzureAiStudioChatCompletionTaskSettings( + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens + ) { + + this.temperature = temperature; + this.topP = topP; + this.doSample = doSample; + this.maxNewTokens = maxNewTokens; + } + + public AzureAiStudioChatCompletionTaskSettings(StreamInput in) throws IOException { + this.temperature = in.readOptionalDouble(); + this.topP = in.readOptionalDouble(); + this.doSample = in.readOptionalBoolean(); + this.maxNewTokens = in.readOptionalInt(); + } + + private final Double temperature; + private final Double topP; + private final Boolean doSample; + private final Integer maxNewTokens; + + public Double temperature() { + return temperature; + } + + public Double topP() { + return topP; + } + + public Boolean doSample() { + return doSample; + } + + public Integer maxNewTokens() { + return maxNewTokens; + } + + public boolean areAnyParametersAvailable() { + return temperature != null && topP != null && doSample != null && maxNewTokens != null; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_AZURE_OPENAI_EMBEDDINGS; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalDouble(temperature); + out.writeOptionalDouble(topP); + out.writeOptionalBoolean(doSample); + out.writeOptionalInt(maxNewTokens); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + if (temperature != null) { + builder.field(TEMPERATURE_FIELD, temperature); + } + if (topP != null) { + builder.field(TOP_P_FIELD, topP); + } + if (doSample != null) { + builder.field(DO_SAMPLE_FIELD, doSample); + } + if (maxNewTokens != null) { + builder.field(MAX_NEW_TOKENS_FIELD, maxNewTokens); + } + + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AzureAiStudioChatCompletionTaskSettings that = (AzureAiStudioChatCompletionTaskSettings) o; + return Objects.equals(temperature, that.temperature) + && Objects.equals(topP, that.topP) + && Objects.equals(doSample, that.doSample) + && Objects.equals(maxNewTokens, that.maxNewTokens); + } + + @Override + public int hashCode() { + return Objects.hash(temperature, topP, doSample, maxNewTokens); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsModel.java new file mode 100644 index 0000000000000..a999b9f0312e6 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsModel.java @@ -0,0 +1,102 @@ +/* + * 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.azureaistudio.embeddings; + +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.action.azureaistudio.AzureAiStudioActionVisitor; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.EMBEDDINGS_URI_PATH; + +public class AzureAiStudioEmbeddingsModel extends AzureAiStudioModel { + + public static AzureAiStudioEmbeddingsModel of(AzureAiStudioEmbeddingsModel model, Map taskSettings) { + if (taskSettings == null || taskSettings.isEmpty()) { + return model; + } + + var requestTaskSettings = AzureAiStudioEmbeddingsRequestTaskSettings.fromMap(taskSettings); + var taskSettingToUse = AzureAiStudioEmbeddingsTaskSettings.of(model.getTaskSettings(), requestTaskSettings); + + return new AzureAiStudioEmbeddingsModel(model, taskSettingToUse); + } + + public AzureAiStudioEmbeddingsModel( + String inferenceEntityId, + TaskType taskType, + String service, + AzureAiStudioEmbeddingsServiceSettings serviceSettings, + AzureAiStudioEmbeddingsTaskSettings taskSettings, + DefaultSecretSettings secrets + ) { + super(new ModelConfigurations(inferenceEntityId, taskType, service, serviceSettings, taskSettings), new ModelSecrets(secrets)); + } + + public AzureAiStudioEmbeddingsModel( + String inferenceEntityId, + TaskType taskType, + String service, + Map serviceSettings, + Map taskSettings, + @Nullable Map secrets, + ConfigurationParseContext context + ) { + this( + inferenceEntityId, + taskType, + service, + AzureAiStudioEmbeddingsServiceSettings.fromMap(serviceSettings, context), + AzureAiStudioEmbeddingsTaskSettings.fromMap(taskSettings), + DefaultSecretSettings.fromMap(secrets) + ); + } + + private AzureAiStudioEmbeddingsModel(AzureAiStudioEmbeddingsModel model, AzureAiStudioEmbeddingsTaskSettings taskSettings) { + super(model, taskSettings, model.getServiceSettings().rateLimitSettings()); + } + + public AzureAiStudioEmbeddingsModel(AzureAiStudioEmbeddingsModel model, AzureAiStudioEmbeddingsServiceSettings serviceSettings) { + super(model, serviceSettings); + } + + @Override + public AzureAiStudioEmbeddingsServiceSettings getServiceSettings() { + return (AzureAiStudioEmbeddingsServiceSettings) super.getServiceSettings(); + } + + @Override + public AzureAiStudioEmbeddingsTaskSettings getTaskSettings() { + return (AzureAiStudioEmbeddingsTaskSettings) super.getTaskSettings(); + } + + @Override + protected URI getEndpointUri() throws URISyntaxException { + if (this.provider == AzureAiStudioProvider.OPENAI || this.endpointType == AzureAiStudioEndpointType.REALTIME) { + return new URI(this.target); + } + + return new URI(this.target + EMBEDDINGS_URI_PATH); + } + + @Override + public ExecutableAction accept(AzureAiStudioActionVisitor creator, Map taskSettings) { + return creator.create(this, taskSettings); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsRequestTaskSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsRequestTaskSettings.java new file mode 100644 index 0000000000000..8c9fd22a7cdf7 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsRequestTaskSettings.java @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.azureaistudio.embeddings; + +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.xpack.inference.services.azureopenai.embeddings.AzureOpenAiEmbeddingsRequestTaskSettings; + +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalString; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.USER_FIELD; + +/** + * This class handles extracting Azure OpenAI task settings from a request. The difference between this class and + * {@link AzureAiStudioEmbeddingsTaskSettings} is that this class considers all fields as optional. It will not throw an error if a field + * is missing. This allows overriding persistent task settings. + * @param user a unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse, if using an OpenAI model + */ +public record AzureAiStudioEmbeddingsRequestTaskSettings(@Nullable String user) { + public static final AzureAiStudioEmbeddingsRequestTaskSettings EMPTY_SETTINGS = new AzureAiStudioEmbeddingsRequestTaskSettings(null); + + /** + * Extracts the task settings from a map. All settings are considered optional and the absence of a setting + * does not throw an error. + * + * @param map the settings received from a request + * @return a {@link AzureOpenAiEmbeddingsRequestTaskSettings} + */ + public static AzureAiStudioEmbeddingsRequestTaskSettings fromMap(Map map) { + if (map.isEmpty()) { + return AzureAiStudioEmbeddingsRequestTaskSettings.EMPTY_SETTINGS; + } + + ValidationException validationException = new ValidationException(); + + String user = extractOptionalString(map, USER_FIELD, ModelConfigurations.TASK_SETTINGS, validationException); + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new AzureAiStudioEmbeddingsRequestTaskSettings(user); + } + +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsServiceSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsServiceSettings.java new file mode 100644 index 0000000000000..1a39cd67a70f3 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsServiceSettings.java @@ -0,0 +1,231 @@ +/* + * 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.azureaistudio.embeddings; + +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.core.Nullable; +import org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.SimilarityMeasure; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.ServiceUtils; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioServiceSettings; +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.DIMENSIONS; +import static org.elasticsearch.xpack.inference.services.ServiceFields.MAX_INPUT_TOKENS; +import static org.elasticsearch.xpack.inference.services.ServiceFields.SIMILARITY; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalBoolean; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractSimilarity; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.removeAsType; + +public class AzureAiStudioEmbeddingsServiceSettings extends AzureAiStudioServiceSettings { + + public static final String NAME = "azure_ai_studio_embeddings_service_settings"; + static final String DIMENSIONS_SET_BY_USER = "dimensions_set_by_user"; + + public static AzureAiStudioEmbeddingsServiceSettings fromMap(Map map, ConfigurationParseContext context) { + ValidationException validationException = new ValidationException(); + + var settings = embeddingSettingsFromMap(map, validationException, context); + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new AzureAiStudioEmbeddingsServiceSettings(settings); + } + + private static AzureAiStudioEmbeddingCommonFields embeddingSettingsFromMap( + Map map, + ValidationException validationException, + ConfigurationParseContext context + ) { + var baseSettings = AzureAiStudioServiceSettings.fromMap(map, validationException, context); + SimilarityMeasure similarity = extractSimilarity(map, ModelConfigurations.SERVICE_SETTINGS, validationException); + Integer dims = removeAsType(map, DIMENSIONS, Integer.class); + Integer maxTokens = removeAsType(map, MAX_INPUT_TOKENS, Integer.class); + + Boolean dimensionsSetByUser = extractOptionalBoolean(map, DIMENSIONS_SET_BY_USER, validationException); + + switch (context) { + case REQUEST -> { + if (dimensionsSetByUser != null) { + validationException.addValidationError( + ServiceUtils.invalidSettingError(DIMENSIONS_SET_BY_USER, ModelConfigurations.SERVICE_SETTINGS) + ); + } + dimensionsSetByUser = dims != null; + } + case PERSISTENT -> { + if (dimensionsSetByUser == null) { + validationException.addValidationError( + ServiceUtils.missingSettingErrorMsg(DIMENSIONS_SET_BY_USER, ModelConfigurations.SERVICE_SETTINGS) + ); + } + } + } + return new AzureAiStudioEmbeddingCommonFields(baseSettings, dims, dimensionsSetByUser, maxTokens, similarity); + } + + private record AzureAiStudioEmbeddingCommonFields( + BaseAzureAiStudioCommonFields baseCommonFields, + @Nullable Integer dimensions, + Boolean dimensionsSetByUser, + @Nullable Integer maxInputTokens, + SimilarityMeasure similarity + ) {} + + public AzureAiStudioEmbeddingsServiceSettings( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + @Nullable Integer dimensions, + Boolean dimensionsSetByUser, + @Nullable Integer maxInputTokens, + @Nullable SimilarityMeasure similarity, + RateLimitSettings rateLimitSettings + ) { + super(target, provider, endpointType, rateLimitSettings); + this.dimensions = dimensions; + this.dimensionsSetByUser = dimensionsSetByUser; + this.maxInputTokens = maxInputTokens; + this.similarity = similarity; + } + + public AzureAiStudioEmbeddingsServiceSettings(StreamInput in) throws IOException { + super(in); + this.dimensions = in.readOptionalVInt(); + this.dimensionsSetByUser = in.readBoolean(); + this.maxInputTokens = in.readOptionalVInt(); + this.similarity = in.readOptionalEnum(SimilarityMeasure.class); + } + + private AzureAiStudioEmbeddingsServiceSettings(AzureAiStudioEmbeddingCommonFields fields) { + this( + fields.baseCommonFields.target(), + fields.baseCommonFields.provider(), + fields.baseCommonFields.endpointType(), + fields.dimensions(), + fields.dimensionsSetByUser(), + fields.maxInputTokens(), + fields.similarity(), + fields.baseCommonFields.rateLimitSettings() + ); + } + + private final Integer dimensions; + private final Boolean dimensionsSetByUser; + private final Integer maxInputTokens; + private final SimilarityMeasure similarity; + + @Override + public SimilarityMeasure similarity() { + return similarity; + } + + public boolean dimensionsSetByUser() { + return this.dimensionsSetByUser; + } + + public Integer dimensions() { + return dimensions; + } + + public Integer maxInputTokens() { + return maxInputTokens; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_AZURE_AI_STUDIO; + } + + @Override + public DenseVectorFieldMapper.ElementType elementType() { + return DenseVectorFieldMapper.ElementType.FLOAT; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeOptionalVInt(dimensions); + out.writeBoolean(dimensionsSetByUser); + out.writeOptionalVInt(maxInputTokens); + out.writeOptionalEnum(similarity); + } + + private void addXContentFragmentOfExposedFields(XContentBuilder builder, Params params) throws IOException { + if (dimensions != null) { + builder.field(DIMENSIONS, dimensions); + } + if (maxInputTokens != null) { + builder.field(MAX_INPUT_TOKENS, maxInputTokens); + } + if (similarity != null) { + builder.field(SIMILARITY, similarity); + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + super.addXContentFields(builder, params); + addXContentFragmentOfExposedFields(builder, params); + builder.field(DIMENSIONS_SET_BY_USER, dimensionsSetByUser); + + builder.endObject(); + return builder; + } + + @Override + protected XContentBuilder toXContentFragmentOfExposedFields(XContentBuilder builder, ToXContent.Params params) throws IOException { + super.addExposedXContentFields(builder, params); + addXContentFragmentOfExposedFields(builder, params); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AzureAiStudioEmbeddingsServiceSettings that = (AzureAiStudioEmbeddingsServiceSettings) o; + + return Objects.equals(target, that.target) + && Objects.equals(provider, that.provider) + && Objects.equals(endpointType, that.endpointType) + && Objects.equals(dimensions, that.dimensions) + && Objects.equals(dimensionsSetByUser, that.dimensionsSetByUser) + && Objects.equals(maxInputTokens, that.maxInputTokens) + && Objects.equals(similarity, that.similarity) + && Objects.equals(rateLimitSettings, that.rateLimitSettings); + } + + @Override + public int hashCode() { + return Objects.hash(target, provider, endpointType, dimensions, dimensionsSetByUser, maxInputTokens, similarity, rateLimitSettings); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsTaskSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsTaskSettings.java new file mode 100644 index 0000000000000..dc001993b366f --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsTaskSettings.java @@ -0,0 +1,109 @@ +/* + * 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.azureaistudio.embeddings; + +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.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.TaskSettings; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.azureopenai.embeddings.AzureOpenAiEmbeddingsTaskSettings; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalString; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.USER_FIELD; + +public class AzureAiStudioEmbeddingsTaskSettings implements TaskSettings { + public static final String NAME = "azure_ai_studio_embeddings_task_settings"; + + public static AzureAiStudioEmbeddingsTaskSettings fromMap(Map map) { + ValidationException validationException = new ValidationException(); + + String user = extractOptionalString(map, USER_FIELD, ModelConfigurations.TASK_SETTINGS, validationException); + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new AzureAiStudioEmbeddingsTaskSettings(user); + } + + /** + * Creates a new {@link AzureOpenAiEmbeddingsTaskSettings} object by overriding the values in originalSettings with the ones + * passed in via requestSettings if the fields are not null. + * + * @param originalSettings the original {@link AzureOpenAiEmbeddingsTaskSettings} from the inference entity configuration from storage + * @param requestSettings the {@link AzureOpenAiEmbeddingsTaskSettings} from the request + * @return a new {@link AzureOpenAiEmbeddingsTaskSettings} + */ + public static AzureAiStudioEmbeddingsTaskSettings of( + AzureAiStudioEmbeddingsTaskSettings originalSettings, + AzureAiStudioEmbeddingsRequestTaskSettings requestSettings + ) { + var userToUse = requestSettings.user() == null ? originalSettings.user : requestSettings.user(); + return new AzureAiStudioEmbeddingsTaskSettings(userToUse); + } + + public AzureAiStudioEmbeddingsTaskSettings(@Nullable String user) { + this.user = user; + } + + public AzureAiStudioEmbeddingsTaskSettings(StreamInput in) throws IOException { + this.user = in.readOptionalString(); + } + + private final String user; + + public String user() { + return this.user; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_AZURE_OPENAI_EMBEDDINGS; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalString(this.user); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (user != null) { + builder.field(USER_FIELD, user); + } + builder.endObject(); + return builder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AzureAiStudioEmbeddingsTaskSettings that = (AzureAiStudioEmbeddingsTaskSettings) o; + return Objects.equals(user, that.user); + } + + @Override + public int hashCode() { + return Objects.hashCode(user); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java index deb1cfb901602..11dbf673ab7bd 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/CohereService.java @@ -32,6 +32,7 @@ import org.elasticsearch.xpack.inference.services.SenderService; import org.elasticsearch.xpack.inference.services.ServiceComponents; import org.elasticsearch.xpack.inference.services.ServiceUtils; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModel; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsModel; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsServiceSettings; import org.elasticsearch.xpack.inference.services.cohere.rerank.CohereRerankModel; @@ -130,6 +131,7 @@ private static CohereModel createModel( context ); case RERANK -> new CohereRerankModel(inferenceEntityId, taskType, NAME, serviceSettings, taskSettings, secretSettings, context); + case COMPLETION -> new CohereCompletionModel(inferenceEntityId, taskType, NAME, serviceSettings, taskSettings, secretSettings); default -> throw new ElasticsearchStatusException(failureMessage, RestStatus.BAD_REQUEST); }; } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionModel.java new file mode 100644 index 0000000000000..761081d4d723c --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionModel.java @@ -0,0 +1,86 @@ +/* + * 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.cohere.completion; + +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.EmptyTaskSettings; +import org.elasticsearch.inference.InputType; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.TaskSettings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.xpack.inference.external.action.ExecutableAction; +import org.elasticsearch.xpack.inference.external.action.cohere.CohereActionVisitor; +import org.elasticsearch.xpack.inference.services.cohere.CohereModel; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; + +import java.net.URI; +import java.util.Map; + +public class CohereCompletionModel extends CohereModel { + + public CohereCompletionModel( + String modelId, + TaskType taskType, + String service, + Map serviceSettings, + Map taskSettings, + @Nullable Map secrets + ) { + this( + modelId, + taskType, + service, + CohereCompletionServiceSettings.fromMap(serviceSettings), + EmptyTaskSettings.INSTANCE, + DefaultSecretSettings.fromMap(secrets) + ); + } + + // should only be used for testing + CohereCompletionModel( + String modelId, + TaskType taskType, + String service, + CohereCompletionServiceSettings serviceSettings, + TaskSettings taskSettings, + @Nullable DefaultSecretSettings secretSettings + ) { + super( + new ModelConfigurations(modelId, taskType, service, serviceSettings, taskSettings), + new ModelSecrets(secretSettings), + secretSettings, + serviceSettings + ); + } + + @Override + public CohereCompletionServiceSettings getServiceSettings() { + return (CohereCompletionServiceSettings) super.getServiceSettings(); + } + + @Override + public TaskSettings getTaskSettings() { + return super.getTaskSettings(); + } + + @Override + public DefaultSecretSettings getSecretSettings() { + return (DefaultSecretSettings) super.getSecretSettings(); + } + + @Override + public ExecutableAction accept(CohereActionVisitor visitor, Map taskSettings, InputType inputType) { + return visitor.create(this, taskSettings); + } + + @Override + public URI uri() { + return getServiceSettings().uri(); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionServiceSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionServiceSettings.java new file mode 100644 index 0000000000000..2a22f6333f1a2 --- /dev/null +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionServiceSettings.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.services.cohere.completion; + +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.core.Nullable; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ServiceSettings; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.inference.services.cohere.CohereRateLimitServiceSettings; +import org.elasticsearch.xpack.inference.services.settings.FilteredXContentObject; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.io.IOException; +import java.net.URI; +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.ServiceFields.URL; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.convertToUri; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.createOptionalUri; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalString; + +public class CohereCompletionServiceSettings extends FilteredXContentObject implements ServiceSettings, CohereRateLimitServiceSettings { + + public static final String NAME = "cohere_completion_service_settings"; + + // Production key rate limits for all endpoints: https://docs.cohere.com/docs/going-live#production-key-specifications + // 10K requests per minute + private static final RateLimitSettings DEFAULT_RATE_LIMIT_SETTINGS = new RateLimitSettings(10_000); + + public static CohereCompletionServiceSettings fromMap(Map map) { + ValidationException validationException = new ValidationException(); + + String url = extractOptionalString(map, URL, ModelConfigurations.SERVICE_SETTINGS, validationException); + URI uri = convertToUri(url, URL, ModelConfigurations.SERVICE_SETTINGS, validationException); + RateLimitSettings rateLimitSettings = RateLimitSettings.of(map, DEFAULT_RATE_LIMIT_SETTINGS, validationException); + String modelId = extractOptionalString(map, MODEL_ID, ModelConfigurations.SERVICE_SETTINGS, validationException); + + if (validationException.validationErrors().isEmpty() == false) { + throw validationException; + } + + return new CohereCompletionServiceSettings(uri, modelId, rateLimitSettings); + } + + private final URI uri; + + private final String modelId; + + private final RateLimitSettings rateLimitSettings; + + public CohereCompletionServiceSettings(@Nullable URI uri, @Nullable String modelId, @Nullable RateLimitSettings rateLimitSettings) { + this.uri = uri; + this.modelId = modelId; + this.rateLimitSettings = Objects.requireNonNullElse(rateLimitSettings, DEFAULT_RATE_LIMIT_SETTINGS); + } + + public CohereCompletionServiceSettings(@Nullable String url, @Nullable String modelId, @Nullable RateLimitSettings rateLimitSettings) { + this(createOptionalUri(url), modelId, rateLimitSettings); + } + + public CohereCompletionServiceSettings(StreamInput in) throws IOException { + uri = createOptionalUri(in.readOptionalString()); + modelId = in.readOptionalString(); + rateLimitSettings = new RateLimitSettings(in); + } + + @Override + public RateLimitSettings rateLimitSettings() { + return rateLimitSettings; + } + + public URI uri() { + return uri; + } + + public String modelId() { + return modelId; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + toXContentFragmentOfExposedFields(builder, params); + rateLimitSettings.toXContent(builder, params); + + builder.endObject(); + return builder; + } + + @Override + public String getWriteableName() { + return NAME; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.ML_INFERENCE_COHERE_COMPLETION_ADDED; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + var uriToWrite = uri != null ? uri.toString() : null; + out.writeOptionalString(uriToWrite); + out.writeOptionalString(modelId); + rateLimitSettings.writeTo(out); + } + + @Override + protected XContentBuilder toXContentFragmentOfExposedFields(XContentBuilder builder, Params params) throws IOException { + if (uri != null) { + builder.field(URL, uri.toString()); + } + + if (modelId != null) { + builder.field(MODEL_ID, modelId); + } + + return builder; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + CohereCompletionServiceSettings that = (CohereCompletionServiceSettings) object; + return Objects.equals(uri, that.uri) + && Objects.equals(modelId, that.modelId) + && Objects.equals(rateLimitSettings, that.rateLimitSettings); + } + + @Override + public int hashCode() { + return Objects.hash(uri, modelId, rateLimitSettings); + } +} diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/InternalServiceSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/InternalServiceSettings.java index 854722d989340..ee7db662b4997 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/InternalServiceSettings.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/InternalServiceSettings.java @@ -41,7 +41,7 @@ protected static void validateParameters(Integer numAllocations, ValidationExcep ); } else if (numAllocations < 1) { validationException.addValidationError( - ServiceUtils.mustBeAPositiveNumberErrorMessage(NUM_ALLOCATIONS, ModelConfigurations.SERVICE_SETTINGS, numAllocations) + ServiceUtils.mustBeAPositiveIntegerErrorMessage(NUM_ALLOCATIONS, ModelConfigurations.SERVICE_SETTINGS, numAllocations) ); } @@ -49,7 +49,7 @@ protected static void validateParameters(Integer numAllocations, ValidationExcep validationException.addValidationError(ServiceUtils.missingSettingErrorMsg(NUM_THREADS, ModelConfigurations.SERVICE_SETTINGS)); } else if (numThreads < 1) { validationException.addValidationError( - ServiceUtils.mustBeAPositiveNumberErrorMessage(NUM_THREADS, ModelConfigurations.SERVICE_SETTINGS, numThreads) + ServiceUtils.mustBeAPositiveIntegerErrorMessage(NUM_THREADS, ModelConfigurations.SERVICE_SETTINGS, numThreads) ); } } diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettings.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettings.java index 985168c7ccfd1..cfc375a525dd6 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettings.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettings.java @@ -19,7 +19,7 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; -import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalPositiveInteger; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalPositiveLong; import static org.elasticsearch.xpack.inference.services.ServiceUtils.removeFromMapOrDefaultEmpty; public class RateLimitSettings implements Writeable, ToXContentFragment { @@ -32,7 +32,7 @@ public class RateLimitSettings implements Writeable, ToXContentFragment { public static RateLimitSettings of(Map map, RateLimitSettings defaultValue, ValidationException validationException) { Map settings = removeFromMapOrDefaultEmpty(map, FIELD_NAME); - var requestsPerMinute = extractOptionalPositiveInteger(settings, REQUESTS_PER_MINUTE_FIELD, FIELD_NAME, validationException); + var requestsPerMinute = extractOptionalPositiveLong(settings, REQUESTS_PER_MINUTE_FIELD, FIELD_NAME, validationException); return requestsPerMinute == null ? defaultValue : new RateLimitSettings(requestsPerMinute); } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/EmptyTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/EmptyTaskSettingsTests.java index 5a51e89f57e11..060dc23b935cc 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/EmptyTaskSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/EmptyTaskSettingsTests.java @@ -29,6 +29,7 @@ protected EmptyTaskSettings createTestInstance() { @Override protected EmptyTaskSettings mutateInstance(EmptyTaskSettings instance) { + // All instances are the same and have no fields, nothing to mutate return null; } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/InferenceActionResponseTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/InferenceActionResponseTests.java index 7016122fedcf8..428dbca892438 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/InferenceActionResponseTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/action/InferenceActionResponseTests.java @@ -53,7 +53,7 @@ protected InferenceAction.Response createTestInstance() { @Override protected InferenceAction.Response mutateInstance(InferenceAction.Response instance) throws IOException { - return null; + return randomValueOtherThan(instance, this::createTestInstance); } @Override diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionAndCreatorTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionAndCreatorTests.java new file mode 100644 index 0000000000000..15d082f455130 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/azureaistudio/AzureAiStudioActionAndCreatorTests.java @@ -0,0 +1,229 @@ +/* + * 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.action.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.http.MockRequest; +import org.elasticsearch.test.http.MockResponse; +import org.elasticsearch.test.http.MockWebServer; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.inference.action.InferenceAction; +import org.elasticsearch.xpack.inference.common.TruncatorTests; +import org.elasticsearch.xpack.inference.external.action.openai.OpenAiChatCompletionActionTests; +import org.elasticsearch.xpack.inference.external.http.HttpClientManager; +import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput; +import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSender; +import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests; +import org.elasticsearch.xpack.inference.logging.ThrottlerManager; +import org.elasticsearch.xpack.inference.services.ServiceComponents; +import org.elasticsearch.xpack.inference.services.ServiceComponentsTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModelTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModelTests; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; +import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl; +import static org.elasticsearch.xpack.inference.external.http.retry.RetrySettingsTests.buildSettingsWithRetryFields; +import static org.elasticsearch.xpack.inference.external.request.azureaistudio.AzureAiStudioRequestFields.API_KEY_HEADER; +import static org.elasticsearch.xpack.inference.results.TextEmbeddingResultsTests.buildExpectation; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class AzureAiStudioActionAndCreatorTests extends ESTestCase { + private static final TimeValue TIMEOUT = new TimeValue(30, TimeUnit.SECONDS); + private final MockWebServer webServer = new MockWebServer(); + private ThreadPool threadPool; + private HttpClientManager clientManager; + + @Before + public void init() throws Exception { + webServer.start(); + threadPool = createThreadPool(inferenceUtilityPool()); + clientManager = HttpClientManager.create(Settings.EMPTY, threadPool, mockClusterServiceEmpty(), mock(ThrottlerManager.class)); + } + + @After + public void shutdown() throws IOException { + clientManager.close(); + terminate(threadPool); + webServer.close(); + } + + public void testEmbeddingsRequestAction() throws IOException { + var senderFactory = new HttpRequestSender.Factory( + ServiceComponentsTests.createWithEmptySettings(threadPool), + clientManager, + mockClusterServiceEmpty() + ); + + var timeoutSettings = buildSettingsWithRetryFields( + TimeValue.timeValueMillis(1), + TimeValue.timeValueMinutes(1), + TimeValue.timeValueSeconds(0) + ); + + var serviceComponents = new ServiceComponents( + threadPool, + mock(ThrottlerManager.class), + timeoutSettings, + TruncatorTests.createTruncator() + ); + + try (var sender = senderFactory.createSender("test_service")) { + sender.start(); + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testEmbeddingsTokenResponseJson)); + + var model = AzureAiStudioEmbeddingsModelTests.createModel( + "id", + "http://will-be-replaced.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey" + ); + model.setURI(getUrl(webServer)); + + var creator = new AzureAiStudioActionCreator(sender, serviceComponents); + var action = creator.create(model, Map.of()); + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat(result.asMap(), is(buildExpectation(List.of(List.of(0.0123F, -0.0123F))))); + 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())); + assertThat(webServer.requests().get(0).getHeader(API_KEY_HEADER), equalTo("apikey")); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + assertThat(requestMap.size(), is(1)); + assertThat(requestMap.get("input"), is(List.of("abc"))); + } + } + + public void testChatCompletionRequestAction() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + var timeoutSettings = buildSettingsWithRetryFields( + TimeValue.timeValueMillis(1), + TimeValue.timeValueMinutes(1), + TimeValue.timeValueSeconds(0) + ); + + var serviceComponents = new ServiceComponents( + threadPool, + mock(ThrottlerManager.class), + timeoutSettings, + TruncatorTests.createTruncator() + ); + + try (var sender = senderFactory.createSender("test_service")) { + sender.start(); + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testCompletionTokenResponseJson)); + var webserverUrl = getUrl(webServer); + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + "http://will-be-replaced.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey" + ); + model.setURI(webserverUrl); + + var creator = new AzureAiStudioActionCreator(sender, serviceComponents); + var action = creator.create(model, Map.of()); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat( + result.asMap(), + is(OpenAiChatCompletionActionTests.buildExpectedChatCompletionResultMap(List.of("test input string"))) + ); + assertThat(webServer.requests(), hasSize(1)); + + MockRequest request = webServer.requests().get(0); + + assertNull(request.getUri().getQuery()); + assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE), equalTo(XContentType.JSON.mediaType())); + assertThat(request.getHeader(HttpHeaders.AUTHORIZATION), equalTo("apikey")); + + var requestMap = entityAsMap(request.getBody()); + assertThat(requestMap.size(), is(1)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abc")))); + } + } + + private static String testEmbeddingsTokenResponseJson = """ + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.0123, + -0.0123 + ] + } + ], + "model": "text-embedding-ada-002-v2", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + """; + + private static String testCompletionTokenResponseJson = """ + { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "test input string", + "role": "assistant", + "tool_calls": null + } + } + ], + "created": 1714006424, + "id": "f92b5b4d-0de3-4152-a3c6-5aae8a74555c", + "model": "", + "object": "chat.completion", + "usage": { + "completion_tokens": 35, + "prompt_tokens": 8, + "total_tokens": 43 + } + }"""; + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreatorTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreatorTests.java index 73b627742ab03..8d63072b5d7aa 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreatorTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereActionCreatorTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests; import org.elasticsearch.xpack.inference.logging.ThrottlerManager; import org.elasticsearch.xpack.inference.services.cohere.CohereTruncation; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModelTests; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingType; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsModelTests; import org.elasticsearch.xpack.inference.services.cohere.embeddings.CohereEmbeddingsTaskSettings; @@ -39,6 +40,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.cohere.CohereCompletionActionTests.buildExpectedChatCompletionResultMap; import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl; import static org.elasticsearch.xpack.inference.results.TextEmbeddingResultsTests.buildExpectation; @@ -148,4 +150,124 @@ public void testCreate_CohereEmbeddingsModel() throws IOException { ); } } + + public void testCreate_CohereCompletionModel_WithModelSpecified() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var sender = senderFactory.createSender("test_service")) { + sender.start(); + + String responseJson = """ + { + "response_id": "some id", + "text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var model = CohereCompletionModelTests.createModel(getUrl(webServer), "secret", "model"); + var actionCreator = new CohereActionCreator(sender, createWithEmptySettings(threadPool)); + var action = actionCreator.create(model, Map.of()); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat(result.asMap(), is(buildExpectedChatCompletionResultMap(List.of("result")))); + assertThat(webServer.requests(), hasSize(1)); + assertNull(webServer.requests().get(0).getUri().getQuery()); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.CONTENT_TYPE), is(XContentType.JSON.mediaType())); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.AUTHORIZATION), is("Bearer secret")); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + assertThat(requestMap, is(Map.of("message", "abc", "model", "model"))); + } + } + + public void testCreate_CohereCompletionModel_WithoutModelSpecified() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var sender = senderFactory.createSender("test_service")) { + sender.start(); + + String responseJson = """ + { + "response_id": "some id", + "text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var model = CohereCompletionModelTests.createModel(getUrl(webServer), "secret", null); + var actionCreator = new CohereActionCreator(sender, createWithEmptySettings(threadPool)); + var action = actionCreator.create(model, Map.of()); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat(result.asMap(), is(buildExpectedChatCompletionResultMap(List.of("result")))); + assertThat(webServer.requests(), hasSize(1)); + assertNull(webServer.requests().get(0).getUri().getQuery()); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.CONTENT_TYPE), is(XContentType.JSON.mediaType())); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.AUTHORIZATION), is("Bearer secret")); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + assertThat(requestMap, is(Map.of("message", "abc"))); + } + } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereCompletionActionTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereCompletionActionTests.java new file mode 100644 index 0000000000000..195f2bab1d6b5 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/action/cohere/CohereCompletionActionTests.java @@ -0,0 +1,353 @@ +/* + * 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.action.cohere; + +import org.apache.http.HttpHeaders; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.http.MockResponse; +import org.elasticsearch.test.http.MockWebServer; +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.ChatCompletionResults; +import org.elasticsearch.xpack.inference.external.http.HttpClientManager; +import org.elasticsearch.xpack.inference.external.http.sender.DocumentsOnlyInput; +import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests; +import org.elasticsearch.xpack.inference.external.http.sender.Sender; +import org.elasticsearch.xpack.inference.external.request.cohere.CohereUtils; +import org.elasticsearch.xpack.inference.logging.ThrottlerManager; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModelTests; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.elasticsearch.core.Strings.format; +import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; +import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +public class CohereCompletionActionTests extends ESTestCase { + + private static final TimeValue TIMEOUT = new TimeValue(30, TimeUnit.SECONDS); + private final MockWebServer webServer = new MockWebServer(); + private ThreadPool threadPool; + private HttpClientManager clientManager; + + @Before + public void init() throws Exception { + webServer.start(); + threadPool = createThreadPool(inferenceUtilityPool()); + clientManager = HttpClientManager.create(Settings.EMPTY, threadPool, mockClusterServiceEmpty(), mock(ThrottlerManager.class)); + } + + @After + public void shutdown() throws IOException { + clientManager.close(); + terminate(threadPool); + webServer.close(); + } + + public void testExecute_ReturnsSuccessfulResponse_WithModelSpecified() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var sender = HttpRequestSenderTests.createSenderWithSingleRequestManager(senderFactory, "test_service")) { + sender.start(); + + String responseJson = """ + { + "response_id": "some id", + "text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var action = createAction(getUrl(webServer), "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat(result.asMap(), is(buildExpectedChatCompletionResultMap(List.of("result")))); + 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())); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.AUTHORIZATION), equalTo("Bearer secret")); + assertThat( + webServer.requests().get(0).getHeader(CohereUtils.REQUEST_SOURCE_HEADER), + equalTo(CohereUtils.ELASTIC_REQUEST_SOURCE) + ); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + assertThat(requestMap, is(Map.of("message", "abc", "model", "model"))); + } + } + + public void testExecute_ReturnsSuccessfulResponse_WithoutModelSpecified() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var sender = HttpRequestSenderTests.createSenderWithSingleRequestManager(senderFactory, "test_service")) { + sender.start(); + + String responseJson = """ + { + "response_id": "some id", + "text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var action = createAction(getUrl(webServer), "secret", null, sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var result = listener.actionGet(TIMEOUT); + + assertThat(result.asMap(), is(buildExpectedChatCompletionResultMap(List.of("result")))); + 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())); + assertThat(webServer.requests().get(0).getHeader(HttpHeaders.AUTHORIZATION), equalTo("Bearer secret")); + assertThat( + webServer.requests().get(0).getHeader(CohereUtils.REQUEST_SOURCE_HEADER), + equalTo(CohereUtils.ELASTIC_REQUEST_SOURCE) + ); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + assertThat(requestMap, is(Map.of("message", "abc"))); + } + } + + public void testExecute_ThrowsURISyntaxException_ForInvalidUrl() throws IOException { + try (var sender = mock(Sender.class)) { + var thrownException = expectThrows(IllegalArgumentException.class, () -> createAction("a^b", "api key", "model", sender)); + assertThat(thrownException.getMessage(), is("unable to parse url [a^b]")); + } + } + + public void testExecute_ThrowsElasticsearchException() { + var sender = mock(Sender.class); + doThrow(new ElasticsearchException("failed")).when(sender).send(any(), any(), any(), any()); + + var action = createAction(getUrl(webServer), "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT)); + + assertThat(thrownException.getMessage(), is("failed")); + } + + public void testExecute_ThrowsElasticsearchException_WhenSenderOnFailureIsCalled() { + var sender = mock(Sender.class); + + doAnswer(invocation -> { + @SuppressWarnings("unchecked") + ActionListener listener = (ActionListener) invocation.getArguments()[2]; + listener.onFailure(new IllegalStateException("failed")); + + return Void.TYPE; + }).when(sender).send(any(), any(), any(), any()); + + var action = createAction(getUrl(webServer), "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT)); + + assertThat(thrownException.getMessage(), is(format("Failed to send Cohere completion request to [%s]", getUrl(webServer)))); + } + + public void testExecute_ThrowsElasticsearchException_WhenSenderOnFailureIsCalled_WhenUrlIsNull() { + var sender = mock(Sender.class); + + doAnswer(invocation -> { + @SuppressWarnings("unchecked") + ActionListener listener = (ActionListener) invocation.getArguments()[2]; + listener.onFailure(new IllegalStateException("failed")); + + return Void.TYPE; + }).when(sender).send(any(), any(), any(), any()); + + var action = createAction(null, "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT)); + + assertThat(thrownException.getMessage(), is(format("Failed to send Cohere completion request", getUrl(webServer)))); + } + + public void testExecute_ThrowsException() { + var sender = mock(Sender.class); + doThrow(new IllegalArgumentException("failed")).when(sender).send(any(), any(), any(), any()); + + var action = createAction(getUrl(webServer), "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT)); + + assertThat(thrownException.getMessage(), is(format("Failed to send Cohere completion request to [%s]", getUrl(webServer)))); + } + + public void testExecute_ThrowsExceptionWithNullUrl() { + var sender = mock(Sender.class); + doThrow(new IllegalArgumentException("failed")).when(sender).send(any(), any(), any(), any()); + + var action = createAction(null, "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var thrownException = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT)); + + assertThat(thrownException.getMessage(), is("Failed to send Cohere completion request")); + } + + public void testExecute_ThrowsException_WhenInputIsGreaterThanOne() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var sender = senderFactory.createSender("test_service")) { + sender.start(); + + String responseJson = """ + { + "response_id": "some id", + "text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var action = createAction(getUrl(webServer), "secret", "model", sender); + + PlainActionFuture listener = new PlainActionFuture<>(); + action.execute(new DocumentsOnlyInput(List.of("abc", "def")), InferenceAction.Request.DEFAULT_TIMEOUT, listener); + + var thrownException = expectThrows(ElasticsearchStatusException.class, () -> listener.actionGet(TIMEOUT)); + + assertThat(thrownException.getMessage(), is("Cohere completion only accepts 1 input")); + assertThat(thrownException.status(), is(RestStatus.BAD_REQUEST)); + } + } + + public static Map buildExpectedChatCompletionResultMap(List results) { + return Map.of( + ChatCompletionResults.COMPLETION, + results.stream().map(result -> Map.of(ChatCompletionResults.Result.RESULT, result)).toList() + ); + } + + private CohereCompletionAction createAction(String url, String apiKey, @Nullable String modelName, Sender sender) { + var model = CohereCompletionModelTests.createModel(url, apiKey, modelName); + + return new CohereCompletionAction(sender, model, threadPool); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestEntityTests.java new file mode 100644 index 0000000000000..3b086f4d3b900 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestEntityTests.java @@ -0,0 +1,227 @@ +/* + * 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.azureaistudio; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.core.Nullable; +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.services.azureaistudio.AzureAiStudioEndpointType; + +import java.io.IOException; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; + +public class AzureAiStudioChatCompletionRequestEntityTests extends ESTestCase { + + public void testToXContent_WhenTokenEndpoint_NoParameters() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity(List.of("abc"), AzureAiStudioEndpointType.TOKEN, null, null, null, null); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedTokenEndpointRequest(List.of("abc"), null, null, null, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenTokenEndpoint_WithTemperatureParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity(List.of("abc"), AzureAiStudioEndpointType.TOKEN, 1.0, null, null, null); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedTokenEndpointRequest(List.of("abc"), 1.0, null, null, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenTokenEndpoint_WithTopPParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity(List.of("abc"), AzureAiStudioEndpointType.TOKEN, null, 2.0, null, null); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedTokenEndpointRequest(List.of("abc"), null, 2.0, null, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenTokenEndpoint_WithDoSampleParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity(List.of("abc"), AzureAiStudioEndpointType.TOKEN, null, null, true, null); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedTokenEndpointRequest(List.of("abc"), null, null, true, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenTokenEndpoint_WithMaxNewTokensParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity(List.of("abc"), AzureAiStudioEndpointType.TOKEN, null, null, null, 512); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedTokenEndpointRequest(List.of("abc"), null, null, null, 512); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenRealtimeEndpoint_NoParameters() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity( + List.of("abc"), + AzureAiStudioEndpointType.REALTIME, + null, + null, + null, + null + ); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedRealtimeEndpointRequest(List.of("abc"), null, null, null, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenRealtimeEndpoint_WithTemperatureParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity( + List.of("abc"), + AzureAiStudioEndpointType.REALTIME, + 1.0, + null, + null, + null + ); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedRealtimeEndpointRequest(List.of("abc"), 1.0, null, null, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenRealtimeEndpoint_WithTopPParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity( + List.of("abc"), + AzureAiStudioEndpointType.REALTIME, + null, + 2.0, + null, + null + ); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedRealtimeEndpointRequest(List.of("abc"), null, 2.0, null, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenRealtimeEndpoint_WithDoSampleParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity( + List.of("abc"), + AzureAiStudioEndpointType.REALTIME, + null, + null, + true, + null + ); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedRealtimeEndpointRequest(List.of("abc"), null, null, true, null); + assertThat(request, is(expectedRequest)); + } + + public void testToXContent_WhenRealtimeEndpoint_WithMaxNewTokensParam() throws IOException { + var entity = new AzureAiStudioChatCompletionRequestEntity( + List.of("abc"), + AzureAiStudioEndpointType.REALTIME, + null, + null, + null, + 512 + ); + var request = getXContentAsString(entity); + var expectedRequest = getExpectedRealtimeEndpointRequest(List.of("abc"), null, null, null, 512); + assertThat(request, is(expectedRequest)); + } + + private String getXContentAsString(AzureAiStudioChatCompletionRequestEntity entity) throws IOException { + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + return Strings.toString(builder); + } + + private String getExpectedTokenEndpointRequest( + List inputs, + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens + ) { + String expected = "{"; + + expected = addMessageInputs("messages", expected, inputs); + expected = addParameters(expected, temperature, topP, doSample, maxNewTokens); + + expected += "}"; + return expected; + } + + private String getExpectedRealtimeEndpointRequest( + List inputs, + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens + ) { + String expected = "{\"input_data\":{"; + + expected = addMessageInputs("input_string", expected, inputs); + expected = addParameters(expected, temperature, topP, doSample, maxNewTokens); + + expected += "}}"; + return expected; + } + + private String addMessageInputs(String fieldName, String expected, List inputs) { + StringBuilder messages = new StringBuilder(Strings.format("\"%s\":[", fieldName)); + var hasOne = false; + for (String input : inputs) { + if (hasOne) { + messages.append(","); + } + messages.append(getMessageString(input)); + hasOne = true; + } + messages.append("]"); + + return expected + messages; + } + + private String getMessageString(String input) { + return Strings.format("{\"content\":\"%s\",\"role\":\"user\"}", input); + } + + private String addParameters(String expected, Double temperature, Double topP, Boolean doSample, Integer maxNewTokens) { + if (temperature == null && topP == null && doSample == null && maxNewTokens == null) { + return expected; + } + + StringBuilder parameters = new StringBuilder(",\"parameters\":{"); + + var hasOne = false; + if (temperature != null) { + parameters.append(Strings.format("\"temperature\":%.1f", temperature)); + hasOne = true; + } + + if (topP != null) { + if (hasOne) { + parameters.append(","); + } + parameters.append(Strings.format("\"top_p\":%.1f", topP)); + hasOne = true; + } + + if (doSample != null) { + if (hasOne) { + parameters.append(","); + } + parameters.append(Strings.format("\"do_sample\":%s", doSample.equals(Boolean.TRUE))); + hasOne = true; + } + + if (maxNewTokens != null) { + if (hasOne) { + parameters.append(","); + } + parameters.append(Strings.format("\"max_new_tokens\":%d", maxNewTokens)); + } + + parameters.append("}"); + + return expected + parameters; + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestTests.java new file mode 100644 index 0000000000000..f3ddf7f9299d9 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioChatCompletionRequestTests.java @@ -0,0 +1,465 @@ +/* + * 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.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.request.HttpRequest; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModelTests; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.elasticsearch.xpack.inference.external.request.azureopenai.AzureOpenAiUtils.API_KEY_HEADER; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioChatCompletionRequestTests extends ESTestCase { + + public void testCreateRequest_WithOpenAiProviderTokenEndpoint_NoParams() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + } + + public void testCreateRequest_WithOpenAiProviderTokenEndpoint_WithTemperatureParam() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 1.0, + null, + null, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(1.0, null, null, null))); + } + + public void testCreateRequest_WithOpenAiProviderTokenEndpoint_WithTopPParam() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + 2.0, + null, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(null, 2.0, null, null))); + } + + public void testCreateRequest_WithOpenAiProviderTokenEndpoint_WithDoSampleParam() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + true, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(null, null, true, null))); + } + + public void testCreateRequest_WithOpenAiProviderTokenEndpoint_WithMaxNewTokensParam() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + null, + 512, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(null, null, null, 512))); + } + + public void testCreateRequest_WithCohereProviderTokenEndpoint_NoParams() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/chat/completions"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + } + + public void testCreateRequest_WithCohereProviderTokenEndpoint_WithTemperatureParam() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 1.0, + null, + null, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/chat/completions"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(1.0, null, null, null))); + } + + public void testCreateRequest_WithCohereProviderTokenEndpoint_WithTopPParam() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + 2.0, + null, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/chat/completions"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(null, 2.0, null, null))); + } + + public void testCreateRequest_WithCohereProviderTokenEndpoint_WithDoSampleParam() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + true, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/chat/completions"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(null, null, true, null))); + } + + public void testCreateRequest_WithCohereProviderTokenEndpoint_WithMaxNewTokensParam() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + null, + 512, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/chat/completions"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("messages"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(requestMap.get("parameters"), is(getParameterMap(null, null, null, 512))); + } + + public void testCreateRequest_WithMistralProviderRealtimeEndpoint_NoParams() throws IOException { + var request = createRequest( + "http://mistral.local/score", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey", + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://mistral.local/score"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.MISTRAL, AzureAiStudioEndpointType.REALTIME, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + + @SuppressWarnings("unchecked") + var input_data = (Map) requestMap.get("input_data"); + assertThat(input_data, aMapWithSize(1)); + assertThat(input_data.get("input_string"), is(List.of(Map.of("role", "user", "content", "abcd")))); + } + + public void testCreateRequest_WithMistralProviderRealtimeEndpoint_WithTemperatureParam() throws IOException { + var request = createRequest( + "http://mistral.local/score", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey", + 1.0, + null, + null, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://mistral.local/score"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.MISTRAL, AzureAiStudioEndpointType.REALTIME, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + + @SuppressWarnings("unchecked") + var input_data = (Map) requestMap.get("input_data"); + assertThat(input_data, aMapWithSize(2)); + assertThat(input_data.get("input_string"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(input_data.get("parameters"), is(getParameterMap(1.0, null, null, null))); + } + + public void testCreateRequest_WithMistralProviderRealtimeEndpoint_WithTopPParam() throws IOException { + var request = createRequest( + "http://mistral.local/score", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey", + null, + 2.0, + null, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://mistral.local/score"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.MISTRAL, AzureAiStudioEndpointType.REALTIME, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + + @SuppressWarnings("unchecked") + var input_data = (Map) requestMap.get("input_data"); + assertThat(input_data, aMapWithSize(2)); + assertThat(input_data.get("input_string"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(input_data.get("parameters"), is(getParameterMap(null, 2.0, null, null))); + } + + public void testCreateRequest_WithMistralProviderRealtimeEndpoint_WithDoSampleParam() throws IOException { + var request = createRequest( + "http://mistral.local/score", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey", + null, + null, + true, + null, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://mistral.local/score"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.MISTRAL, AzureAiStudioEndpointType.REALTIME, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + + @SuppressWarnings("unchecked") + var input_data = (Map) requestMap.get("input_data"); + assertThat(input_data, aMapWithSize(2)); + assertThat(input_data.get("input_string"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(input_data.get("parameters"), is(getParameterMap(null, null, true, null))); + } + + public void testCreateRequest_WithMistralProviderRealtimeEndpoint_WithMaxNewTokensParam() throws IOException { + var request = createRequest( + "http://mistral.local/score", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey", + null, + null, + null, + 512, + "abcd" + ); + var httpRequest = request.createHttpRequest(); + + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://mistral.local/score"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.MISTRAL, AzureAiStudioEndpointType.REALTIME, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + + @SuppressWarnings("unchecked") + var input_data = (Map) requestMap.get("input_data"); + assertThat(input_data, aMapWithSize(2)); + assertThat(input_data.get("input_string"), is(List.of(Map.of("role", "user", "content", "abcd")))); + assertThat(input_data.get("parameters"), is(getParameterMap(null, null, null, 512))); + } + + private HttpPost validateRequestUrlAndContentType(HttpRequest request, String expectedUrl) throws IOException { + assertThat(request.httpRequestBase(), instanceOf(HttpPost.class)); + var httpPost = (HttpPost) request.httpRequestBase(); + assertThat(httpPost.getURI().toString(), is(expectedUrl)); + assertThat(httpPost.getLastHeader(HttpHeaders.CONTENT_TYPE).getValue(), is(XContentType.JSON.mediaType())); + return httpPost; + } + + private void validateRequestApiKey( + HttpPost httpPost, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey + ) { + if (endpointType == AzureAiStudioEndpointType.TOKEN) { + if (provider == AzureAiStudioProvider.OPENAI) { + assertThat(httpPost.getLastHeader(API_KEY_HEADER).getValue(), is(apiKey)); + } else { + assertThat(httpPost.getLastHeader(HttpHeaders.AUTHORIZATION).getValue(), is(apiKey)); + } + } else { + assertThat(httpPost.getLastHeader(HttpHeaders.AUTHORIZATION).getValue(), is("Bearer " + apiKey)); + } + } + + private Map getParameterMap( + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens + ) { + var map = new HashMap(); + if (temperature != null) { + map.put("temperature", temperature); + } + if (topP != null) { + map.put("top_p", topP); + } + if (doSample != null) { + map.put("do_sample", doSample); + } + if (maxNewTokens != null) { + map.put("max_new_tokens", maxNewTokens); + } + return map; + } + + public static AzureAiStudioChatCompletionRequest createRequest( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey, + String input + ) { + return createRequest(target, provider, endpointType, apiKey, null, null, null, null, input); + } + + public static AzureAiStudioChatCompletionRequest createRequest( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey, + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens, + String input + ) { + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + target, + provider, + endpointType, + apiKey, + temperature, + topP, + doSample, + maxNewTokens, + null + ); + return new AzureAiStudioChatCompletionRequest(model, List.of(input)); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestEntityTests.java new file mode 100644 index 0000000000000..b2df7f7c27564 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestEntityTests.java @@ -0,0 +1,77 @@ +/* + * 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.azureaistudio; + +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 java.io.IOException; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; + +public class AzureAiStudioEmbeddingsRequestEntityTests extends ESTestCase { + public void testXContent_WritesUserWhenDefined() throws IOException { + var entity = new AzureAiStudioEmbeddingsRequestEntity(List.of("abc"), "testuser", null, false); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"input":["abc"],"user":"testuser"}""")); + } + + public void testXContent_DoesNotWriteUserWhenItIsNull() throws IOException { + var entity = new AzureAiStudioEmbeddingsRequestEntity(List.of("abc"), null, null, false); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"input":["abc"]}""")); + } + + public void testXContent_DoesNotWriteDimensionsWhenNotSetByUser() throws IOException { + var entity = new AzureAiStudioEmbeddingsRequestEntity(List.of("abc"), null, 100, false); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"input":["abc"]}""")); + } + + public void testXContent_DoesNotWriteDimensionsWhenNull_EvenIfSetByUserIsTrue() throws IOException { + var entity = new AzureAiStudioEmbeddingsRequestEntity(List.of("abc"), null, null, true); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"input":["abc"]}""")); + } + + public void testXContent_WritesDimensionsWhenNonNull_AndSetByUserIsTrue() throws IOException { + var entity = new AzureAiStudioEmbeddingsRequestEntity(List.of("abc"), null, 100, true); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"input":["abc"],"dimensions":100}""")); + } + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestTests.java new file mode 100644 index 0000000000000..524d813a4da1f --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/azureaistudio/AzureAiStudioEmbeddingsRequestTests.java @@ -0,0 +1,185 @@ +/* + * 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.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.common.Truncator; +import org.elasticsearch.xpack.inference.common.TruncatorTests; +import org.elasticsearch.xpack.inference.external.request.HttpRequest; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModelTests; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.elasticsearch.xpack.inference.external.request.azureopenai.AzureOpenAiUtils.API_KEY_HEADER; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioEmbeddingsRequestTests extends ESTestCase { + + public void testCreateRequest_WithOpenAiProvider_NoAdditionalParams() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd", + null + ); + var httpRequest = request.createHttpRequest(); + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + assertThat(requestMap.get("input"), is(List.of("abcd"))); + } + + public void testCreateRequest_WithOpenAiProvider_WithUserParam() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd", + "userid" + ); + var httpRequest = request.createHttpRequest(); + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://openaitarget.local"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.OPENAI, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("input"), is(List.of("abcd"))); + assertThat(requestMap.get("user"), is("userid")); + } + + public void testCreateRequest_WithCohereProvider_NoAdditionalParams() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd", + null + ); + var httpRequest = request.createHttpRequest(); + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/embeddings"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(1)); + assertThat(requestMap.get("input"), is(List.of("abcd"))); + } + + public void testCreateRequest_WithCohereProvider_WithUserParam() throws IOException { + var request = createRequest( + "http://coheretarget.local", + AzureAiStudioProvider.COHERE, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd", + "userid" + ); + var httpRequest = request.createHttpRequest(); + var httpPost = validateRequestUrlAndContentType(httpRequest, "http://coheretarget.local/v1/embeddings"); + validateRequestApiKey(httpPost, AzureAiStudioProvider.COHERE, "apikey"); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, aMapWithSize(2)); + assertThat(requestMap.get("input"), is(List.of("abcd"))); + assertThat(requestMap.get("user"), is("userid")); + } + + public void testTruncate_ReducesInputTextSizeByHalf() throws IOException { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd", + null + ); + 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(1)); + assertThat(requestMap.get("input"), is(List.of("ab"))); + } + + public void testIsTruncated_ReturnsTrue() { + var request = createRequest( + "http://openaitarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + "abcd", + null + ); + assertFalse(request.getTruncationInfo()[0]); + + var truncatedRequest = request.truncate(); + assertTrue(truncatedRequest.getTruncationInfo()[0]); + } + + private HttpPost validateRequestUrlAndContentType(HttpRequest request, String expectedUrl) throws IOException { + assertThat(request.httpRequestBase(), instanceOf(HttpPost.class)); + var httpPost = (HttpPost) request.httpRequestBase(); + assertThat(httpPost.getURI().toString(), is(expectedUrl)); + assertThat(httpPost.getLastHeader(HttpHeaders.CONTENT_TYPE).getValue(), is(XContentType.JSON.mediaType())); + return httpPost; + } + + private void validateRequestApiKey(HttpPost httpPost, AzureAiStudioProvider provider, String apiKey) { + if (provider == AzureAiStudioProvider.OPENAI) { + assertThat(httpPost.getLastHeader(API_KEY_HEADER).getValue(), is(apiKey)); + } else { + assertThat(httpPost.getLastHeader(HttpHeaders.AUTHORIZATION).getValue(), is(apiKey)); + } + } + + public static AzureAiStudioEmbeddingsRequest createRequest( + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey, + String input, + @Nullable String user + ) { + var model = AzureAiStudioEmbeddingsModelTests.createModel( + "id", + target, + provider, + endpointType, + apiKey, + null, + false, + null, + null, + user, + null + ); + return new AzureAiStudioEmbeddingsRequest( + TruncatorTests.createTruncator(), + new Truncator.TruncationResult(List.of(input), new boolean[] { false }), + model + ); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereCompletionRequestEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereCompletionRequestEntityTests.java new file mode 100644 index 0000000000000..dbe6a9438d884 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereCompletionRequestEntityTests.java @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.request.cohere; + +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.cohere.completion.CohereCompletionRequestEntity; + +import java.io.IOException; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; + +public class CohereCompletionRequestEntityTests extends ESTestCase { + + public void testXContent_WritesAllFields() throws IOException { + var entity = new CohereCompletionRequestEntity(List.of("some input"), "model"); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"message":"some input","model":"model"}""")); + } + + public void testXContent_DoesNotWriteModelIfNotSpecified() throws IOException { + var entity = new CohereCompletionRequestEntity(List.of("some input"), null); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"message":"some input"}""")); + } + + public void testXContent_ThrowsIfInputIsNull() { + expectThrows(NullPointerException.class, () -> new CohereCompletionRequestEntity(null, null)); + } + + public void testXContent_ThrowsIfMessageInInputIsNull() { + expectThrows(NullPointerException.class, () -> new CohereCompletionRequestEntity(List.of((String) null), null)); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereCompletionRequestTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereCompletionRequestTests.java new file mode 100644 index 0000000000000..d6d0d5c00eaf4 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereCompletionRequestTests.java @@ -0,0 +1,74 @@ +/* + * 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.cohere; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.request.cohere.completion.CohereCompletionRequest; +import org.elasticsearch.xpack.inference.services.cohere.completion.CohereCompletionModelTests; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; + +public class CohereCompletionRequestTests extends ESTestCase { + + public void testCreateRequest_UrlDefined() throws IOException { + var request = new CohereCompletionRequest(List.of("abc"), CohereCompletionModelTests.createModel("url", "secret", null)); + + var httpRequest = request.createHttpRequest(); + assertThat(httpRequest.httpRequestBase(), instanceOf(HttpPost.class)); + + var httpPost = (HttpPost) httpRequest.httpRequestBase(); + + assertThat(httpPost.getURI().toString(), is("url")); + assertThat(httpPost.getLastHeader(HttpHeaders.CONTENT_TYPE).getValue(), is(XContentType.JSON.mediaType())); + assertThat(httpPost.getLastHeader(HttpHeaders.AUTHORIZATION).getValue(), is("Bearer secret")); + assertThat(httpPost.getLastHeader(CohereUtils.REQUEST_SOURCE_HEADER).getValue(), is(CohereUtils.ELASTIC_REQUEST_SOURCE)); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, is(Map.of("message", "abc"))); + } + + public void testCreateRequest_ModelDefined() throws IOException { + var request = new CohereCompletionRequest(List.of("abc"), CohereCompletionModelTests.createModel("url", "secret", "model")); + + var httpRequest = request.createHttpRequest(); + assertThat(httpRequest.httpRequestBase(), instanceOf(HttpPost.class)); + + var httpPost = (HttpPost) httpRequest.httpRequestBase(); + + assertThat(httpPost.getURI().toString(), is("url")); + assertThat(httpPost.getLastHeader(HttpHeaders.CONTENT_TYPE).getValue(), is(XContentType.JSON.mediaType())); + assertThat(httpPost.getLastHeader(HttpHeaders.AUTHORIZATION).getValue(), is("Bearer secret")); + assertThat(httpPost.getLastHeader(CohereUtils.REQUEST_SOURCE_HEADER).getValue(), is(CohereUtils.ELASTIC_REQUEST_SOURCE)); + + var requestMap = entityAsMap(httpPost.getEntity().getContent()); + assertThat(requestMap, is(Map.of("message", "abc", "model", "model"))); + } + + public void testTruncate_ReturnsSameInstance() { + var request = new CohereCompletionRequest(List.of("abc"), CohereCompletionModelTests.createModel("url", "secret", "model")); + var truncatedRequest = request.truncate(); + + assertThat(truncatedRequest, sameInstance(request)); + } + + public void testTruncationInfo_ReturnsNull() { + var request = new CohereCompletionRequest(List.of("abc"), CohereCompletionModelTests.createModel("url", "secret", "model")); + + assertNull(request.getTruncationInfo()); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRequestTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRequestTests.java new file mode 100644 index 0000000000000..444fee7cac3c7 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/request/cohere/CohereRequestTests.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.request.cohere; + +import org.apache.http.HttpHeaders; +import org.apache.http.client.methods.HttpPost; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.inference.external.cohere.CohereAccount; + +import java.net.URI; + +import static org.hamcrest.Matchers.is; + +public class CohereRequestTests extends ESTestCase { + + public void testDecorateWithAuthHeader() { + var request = new HttpPost("http://www.abc.com"); + + CohereRequest.decorateWithAuthHeader( + request, + new CohereAccount(URI.create("http://www.abc.com"), new SecureString(new char[] { 'a', 'b', 'c' })) + ); + + assertThat(request.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue(), is(XContentType.JSON.mediaType())); + assertThat(request.getFirstHeader(HttpHeaders.AUTHORIZATION).getValue(), is("Bearer abc")); + assertThat(request.getFirstHeader(CohereUtils.REQUEST_SOURCE_HEADER).getValue(), is(CohereUtils.ELASTIC_REQUEST_SOURCE)); + } + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiErrorResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiErrorResponseEntityTests.java new file mode 100644 index 0000000000000..fd133a26f5532 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiErrorResponseEntityTests.java @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.external.response; + +import org.apache.http.HttpResponse; +import org.elasticsearch.common.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.external.http.HttpResult; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class AzureAndOpenAiErrorResponseEntityTests extends ESTestCase { + + private static HttpResult getMockResult(String jsonString) { + var response = mock(HttpResponse.class); + return new HttpResult(response, Strings.toUTF8Bytes(jsonString)); + } + + public void testErrorResponse_ExtractsError() { + var result = getMockResult(""" + {"error":{"message":"test_error_message"}}"""); + + var error = AzureAndOpenAiErrorResponseEntity.fromResponse(result); + assertNotNull(error); + assertThat(error.getErrorMessage(), is("test_error_message")); + } + + public void testErrorResponse_ReturnsNullIfNoError() { + var result = getMockResult(""" + {"noerror":true}"""); + + var error = AzureAndOpenAiErrorResponseEntity.fromResponse(result); + assertNull(error); + } + + public void testErrorResponse_ReturnsNullIfNotJson() { + var result = getMockResult("not a json string"); + + var error = AzureAndOpenAiErrorResponseEntity.fromResponse(result); + assertNull(error); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiExternalResponseHandlerTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiExternalResponseHandlerTests.java new file mode 100644 index 0000000000000..4c9fb143c3a5c --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/AzureAndOpenAiExternalResponseHandlerTests.java @@ -0,0 +1,245 @@ +/* + * 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; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.message.BasicHeader; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.external.http.HttpResult; +import org.elasticsearch.xpack.inference.external.http.retry.ContentTooLargeException; +import org.elasticsearch.xpack.inference.external.http.retry.RetryException; +import org.elasticsearch.xpack.inference.external.request.RequestTests; + +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.core.Is.is; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AzureAndOpenAiExternalResponseHandlerTests extends ESTestCase { + + public void testCheckForFailureStatusCode() { + var statusLine = mock(StatusLine.class); + + var httpResponse = mock(HttpResponse.class); + when(httpResponse.getStatusLine()).thenReturn(statusLine); + var header = mock(Header.class); + when(header.getElements()).thenReturn(new HeaderElement[] {}); + when(httpResponse.getFirstHeader(anyString())).thenReturn(header); + + var mockRequest = RequestTests.mockRequest("id"); + var httpResult = new HttpResult(httpResponse, new byte[] {}); + var handler = new AzureAndOpenAiExternalResponseHandler( + "", + (request, result) -> null, + AzureAndOpenAiErrorResponseEntity::fromResponse + ); + + // 200 ok + when(statusLine.getStatusCode()).thenReturn(200); + handler.checkForFailureStatusCode(mockRequest, httpResult); + // 503 + when(statusLine.getStatusCode()).thenReturn(503); + var retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertTrue(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received a server busy error status code for request from inference entity id [id] status [503]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.BAD_REQUEST)); + // 501 + when(statusLine.getStatusCode()).thenReturn(501); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertFalse(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received a server error status code for request from inference entity id [id] status [501]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.BAD_REQUEST)); + // 500 + when(statusLine.getStatusCode()).thenReturn(500); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertTrue(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received a server error status code for request from inference entity id [id] status [500]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.BAD_REQUEST)); + // 429 + when(statusLine.getStatusCode()).thenReturn(429); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertTrue(retryException.shouldRetry()); + assertThat(retryException.getCause().getMessage(), containsString("Received a rate limit status code.")); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.TOO_MANY_REQUESTS)); + // 413 + when(statusLine.getStatusCode()).thenReturn(413); + retryException = expectThrows(ContentTooLargeException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertTrue(retryException.shouldRetry()); + assertThat(retryException.getCause().getMessage(), containsString("Received a content too large status code")); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.REQUEST_ENTITY_TOO_LARGE)); + // 400 content too large + retryException = expectThrows( + ContentTooLargeException.class, + () -> handler.checkForFailureStatusCode(mockRequest, createContentTooLargeResult(400)) + ); + assertTrue(retryException.shouldRetry()); + assertThat(retryException.getCause().getMessage(), containsString("Received a content too large status code")); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.BAD_REQUEST)); + // 400 generic bad request should not be marked as a content too large + when(statusLine.getStatusCode()).thenReturn(400); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertFalse(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received an unsuccessful status code for request from inference entity id [id] status [400]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.BAD_REQUEST)); + // 400 is not flagged as a content too large when the error message is different + when(statusLine.getStatusCode()).thenReturn(400); + retryException = expectThrows( + RetryException.class, + () -> handler.checkForFailureStatusCode(mockRequest, createResult(400, "blah")) + ); + assertFalse(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received an unsuccessful status code for request from inference entity id [id] status [400]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.BAD_REQUEST)); + // 401 + when(statusLine.getStatusCode()).thenReturn(401); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertFalse(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received an authentication error status code for request from inference entity id [id] status [401]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.UNAUTHORIZED)); + // 300 + when(statusLine.getStatusCode()).thenReturn(300); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertFalse(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Unhandled redirection for request from inference entity id [id] status [300]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.MULTIPLE_CHOICES)); + // 402 + when(statusLine.getStatusCode()).thenReturn(402); + retryException = expectThrows(RetryException.class, () -> handler.checkForFailureStatusCode(mockRequest, httpResult)); + assertFalse(retryException.shouldRetry()); + assertThat( + retryException.getCause().getMessage(), + containsString("Received an unsuccessful status code for request from inference entity id [id] status [402]") + ); + assertThat(((ElasticsearchStatusException) retryException.getCause()).status(), is(RestStatus.PAYMENT_REQUIRED)); + } + + public void testBuildRateLimitErrorMessage() { + int statusCode = 429; + var statusLine = mock(StatusLine.class); + when(statusLine.getStatusCode()).thenReturn(statusCode); + var response = mock(HttpResponse.class); + when(response.getStatusLine()).thenReturn(statusLine); + var httpResult = new HttpResult(response, new byte[] {}); + + { + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REQUESTS_LIMIT)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.REQUESTS_LIMIT, "3000") + ); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_REQUESTS)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_REQUESTS, "2999") + ); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.TOKENS_LIMIT)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.TOKENS_LIMIT, "10000") + ); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_TOKENS)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_TOKENS, "99800") + ); + + var error = AzureAndOpenAiExternalResponseHandler.buildRateLimitErrorMessage(httpResult); + assertThat( + error, + containsString("Token limit [10000], remaining tokens [99800]. Request limit [3000], remaining requests [2999]") + ); + } + + { + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.TOKENS_LIMIT)).thenReturn(null); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_TOKENS)).thenReturn(null); + var error = AzureAndOpenAiExternalResponseHandler.buildRateLimitErrorMessage(httpResult); + assertThat( + error, + containsString("Token limit [unknown], remaining tokens [unknown]. Request limit [3000], remaining requests [2999]") + ); + } + + { + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REQUESTS_LIMIT)).thenReturn(null); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_REQUESTS)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_REQUESTS, "2999") + ); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.TOKENS_LIMIT)).thenReturn(null); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_TOKENS)).thenReturn(null); + var error = AzureAndOpenAiExternalResponseHandler.buildRateLimitErrorMessage(httpResult); + assertThat(error, containsString("Remaining tokens [unknown]. Remaining requests [2999]")); + } + + { + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REQUESTS_LIMIT)).thenReturn(null); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_REQUESTS)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_REQUESTS, "2999") + ); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.TOKENS_LIMIT)).thenReturn( + new BasicHeader(AzureAndOpenAiExternalResponseHandler.TOKENS_LIMIT, "10000") + ); + when(response.getFirstHeader(AzureAndOpenAiExternalResponseHandler.REMAINING_TOKENS)).thenReturn(null); + var error = AzureAndOpenAiExternalResponseHandler.buildRateLimitErrorMessage(httpResult); + assertThat( + error, + containsString("Token limit [10000], remaining tokens [unknown]. Request limit [unknown], remaining requests [2999]") + ); + } + } + + private static HttpResult createContentTooLargeResult(int statusCode) { + return createResult( + statusCode, + "This model's maximum context length is 8192 tokens, however you requested 13531 tokens (13531 in your prompt;" + + "0 for the completion). Please reduce your prompt; or completion length." + ); + } + + private static HttpResult createResult(int statusCode, String message) { + var statusLine = mock(StatusLine.class); + when(statusLine.getStatusCode()).thenReturn(statusCode); + var httpResponse = mock(HttpResponse.class); + when(httpResponse.getStatusLine()).thenReturn(statusLine); + + String responseJson = Strings.format(""" + { + "error": { + "message": "%s", + "type": "content_too_large", + "param": null, + "code": null + } + } + """, message); + + return new HttpResult(httpResponse, responseJson.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioChatCompletionResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioChatCompletionResponseEntityTests.java new file mode 100644 index 0000000000000..7d5aafa181b19 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioChatCompletionResponseEntityTests.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.inference.external.response.azureaistudio; + +import org.apache.http.HttpResponse; +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.azureaistudio.AzureAiStudioChatCompletionRequest; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModelTests; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class AzureAiStudioChatCompletionResponseEntityTests extends ESTestCase { + + public void testCompletionResponse_FromTokenEndpoint() throws IOException { + var entity = new AzureAiStudioChatCompletionResponseEntity(); + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + "http://testopenai.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey" + ); + var request = new AzureAiStudioChatCompletionRequest(model, List.of("test input")); + var result = (ChatCompletionResults) entity.apply( + request, + new HttpResult(mock(HttpResponse.class), testTokenResponseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(result.getResults().size(), equalTo(1)); + assertThat(result.getResults().get(0).content(), is("test input string")); + } + + public void testCompletionResponse_FromRealtimeEndpoint() throws IOException { + var entity = new AzureAiStudioChatCompletionResponseEntity(); + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + "http://testmistral.local", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey" + ); + var request = new AzureAiStudioChatCompletionRequest(model, List.of("test input")); + var result = (ChatCompletionResults) entity.apply( + request, + new HttpResult(mock(HttpResponse.class), testRealtimeResponseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(result.getResults().size(), equalTo(1)); + assertThat(result.getResults().get(0).content(), is("test realtime response")); + } + + private static String testRealtimeResponseJson = """ + { + "output": "test realtime response" + } + """; + + private static String testTokenResponseJson = """ + { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "test input string", + "role": "assistant", + "tool_calls": null + } + } + ], + "created": 1714006424, + "id": "f92b5b4d-0de3-4152-a3c6-5aae8a74555c", + "model": "", + "object": "chat.completion", + "usage": { + "completion_tokens": 35, + "prompt_tokens": 8, + "total_tokens": 43 + } + }"""; +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioEmbeddingsResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioEmbeddingsResponseEntityTests.java new file mode 100644 index 0000000000000..fd31743616e6e --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/azureaistudio/AzureAiStudioEmbeddingsResponseEntityTests.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.inference.external.response.azureaistudio; + +import org.apache.http.HttpResponse; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.inference.results.TextEmbeddingResults; +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 java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +/** + * Note - the underlying AzureAiStudioEmbeddingsResponseEntity uses the same + * response entity parser as OpenAI. This test just performs a smoke + * test of the wrapper + */ +public class AzureAiStudioEmbeddingsResponseEntityTests extends ESTestCase { + public void testFromResponse_CreatesResultsForASingleItem() throws IOException { + String responseJson = """ + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.014539449, + -0.015288644 + ] + } + ], + "model": "text-embedding-ada-002-v2", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + """; + + var entity = new AzureAiStudioEmbeddingsResponseEntity(); + + var parsedResults = (TextEmbeddingResults) entity.apply( + mock(Request.class), + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(parsedResults.embeddings(), is(List.of(new TextEmbeddingResults.Embedding(List.of(0.014539449F, -0.015288644F))))); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/cohere/CohereCompletionResponseEntityTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/cohere/CohereCompletionResponseEntityTests.java new file mode 100644 index 0000000000000..70e1656195c3c --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/external/response/cohere/CohereCompletionResponseEntityTests.java @@ -0,0 +1,159 @@ +/* + * 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.cohere; + +import org.apache.http.HttpResponse; +import org.elasticsearch.common.ParsingException; +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 CohereCompletionResponseEntityTests extends ESTestCase { + + public void testFromResponse_CreatesResponseEntityForText() throws IOException { + String responseJson = """ + { + "response_id": "some id", + "text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "some input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + + ChatCompletionResults chatCompletionResults = CohereCompletionResponseEntity.fromResponse( + mock(Request.class), + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ); + + assertThat(chatCompletionResults.getResults().size(), is(1)); + assertThat(chatCompletionResults.getResults().get(0).content(), is("result")); + } + + public void testFromResponse_FailsWhenTextIsNotPresent() { + String responseJson = """ + { + "response_id": "some id", + "not_text": "result", + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "some input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + + var thrownException = expectThrows( + IllegalStateException.class, + () -> CohereCompletionResponseEntity.fromResponse( + mock(Request.class), + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ) + ); + + assertThat(thrownException.getMessage(), is("Failed to find required field [text] in Cohere chat response")); + } + + public void testFromResponse_FailsWhenTextIsNotAString() { + String responseJson = """ + { + "response_id": "some id", + "text": { + "text": "result" + }, + "generation_id": "some id", + "chat_history": [ + { + "role": "USER", + "message": "some input" + }, + { + "role": "CHATBOT", + "message": "result" + } + ], + "finish_reason": "COMPLETE", + "meta": { + "api_version": { + "version": "1" + }, + "billed_units": { + "input_tokens": 4, + "output_tokens": 191 + }, + "tokens": { + "input_tokens": 70, + "output_tokens": 191 + } + } + } + """; + + var thrownException = expectThrows( + ParsingException.class, + () -> CohereCompletionResponseEntity.fromResponse( + mock(Request.class), + new HttpResult(mock(HttpResponse.class), responseJson.getBytes(StandardCharsets.UTF_8)) + ) + ); + + assertThat( + thrownException.getMessage(), + is("Failed to parse object: expecting token of type [VALUE_STRING] but found [START_OBJECT]") + ); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedSparseEmbeddingResultsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedSparseEmbeddingResultsTests.java index 9484763912cda..4be38b9d0e9d0 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedSparseEmbeddingResultsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedSparseEmbeddingResultsTests.java @@ -132,6 +132,6 @@ protected ChunkedSparseEmbeddingResults createTestInstance() { @Override protected ChunkedSparseEmbeddingResults mutateInstance(ChunkedSparseEmbeddingResults instance) throws IOException { - return null; + return randomValueOtherThan(instance, ChunkedSparseEmbeddingResultsTests::createRandomResults); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingByteResultsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingByteResultsTests.java index c908d2c85f620..05b86217862e9 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingByteResultsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingByteResultsTests.java @@ -131,6 +131,6 @@ protected ChunkedTextEmbeddingByteResults createTestInstance() { @Override protected ChunkedTextEmbeddingByteResults mutateInstance(ChunkedTextEmbeddingByteResults instance) throws IOException { - return null; + return randomValueOtherThan(instance, ChunkedTextEmbeddingByteResultsTests::createRandomResults); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingFloatResultsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingFloatResultsTests.java index 9b18f5536713e..966a639713ba6 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingFloatResultsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingFloatResultsTests.java @@ -51,6 +51,6 @@ protected ChunkedTextEmbeddingFloatResults createTestInstance() { @Override protected ChunkedTextEmbeddingFloatResults mutateInstance(ChunkedTextEmbeddingFloatResults instance) throws IOException { - return null; + return randomValueOtherThan(instance, this::createTestInstance); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingResultsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingResultsTests.java index 9e827b51d50f6..fd0c8b19aae24 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingResultsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/results/ChunkedTextEmbeddingResultsTests.java @@ -159,6 +159,6 @@ protected ChunkedTextEmbeddingResults createTestInstance() { @Override protected ChunkedTextEmbeddingResults mutateInstance(ChunkedTextEmbeddingResults instance) throws IOException { - return null; + return randomValueOtherThan(instance, ChunkedTextEmbeddingResultsTests::createRandomResults); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/ServiceUtilsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/ServiceUtilsTests.java index bf9fdbe7235b6..6f05ab79629e6 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/ServiceUtilsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/ServiceUtilsTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.ValidationException; +import org.elasticsearch.core.Booleans; import org.elasticsearch.core.TimeValue; import org.elasticsearch.inference.InferenceService; import org.elasticsearch.inference.InferenceServiceResults; @@ -32,6 +33,7 @@ import static org.elasticsearch.xpack.inference.services.ServiceUtils.createUri; import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalEnum; import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalPositiveInteger; +import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalPositiveLong; import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalString; import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractOptionalTimeValue; import static org.elasticsearch.xpack.inference.services.ServiceUtils.extractRequiredSecureString; @@ -193,6 +195,95 @@ public void testRemoveAsTypeMissingReturnsNull() { assertThat(map.entrySet(), hasSize(3)); } + public void testRemoveAsOneOfTypes_Validation_WithCorrectTypes() { + Map map = new HashMap<>(Map.of("a", 5, "b", "a string", "c", Boolean.TRUE, "d", 1.0)); + ValidationException validationException = new ValidationException(); + + Integer i = (Integer) ServiceUtils.removeAsOneOfTypes(map, "a", List.of(String.class, Integer.class), validationException); + assertEquals(Integer.valueOf(5), i); + assertNull(map.get("a")); // field has been removed + + String str = (String) ServiceUtils.removeAsOneOfTypes(map, "b", List.of(Integer.class, String.class), validationException); + assertEquals("a string", str); + assertNull(map.get("b")); + + Boolean b = (Boolean) ServiceUtils.removeAsOneOfTypes(map, "c", List.of(String.class, Boolean.class), validationException); + assertEquals(Boolean.TRUE, b); + assertNull(map.get("c")); + + Double d = (Double) ServiceUtils.removeAsOneOfTypes(map, "d", List.of(Booleans.class, Double.class), validationException); + assertEquals(Double.valueOf(1.0), d); + assertNull(map.get("d")); + + assertThat(map.entrySet(), empty()); + } + + public void testRemoveAsOneOfTypes_Validation_WithIncorrectType() { + Map map = new HashMap<>(Map.of("a", 5, "b", "a string", "c", Boolean.TRUE, "d", 5.0, "e", 5)); + + var validationException = new ValidationException(); + Object result = ServiceUtils.removeAsOneOfTypes(map, "a", List.of(String.class, Boolean.class), validationException); + assertNull(result); + assertThat(validationException.validationErrors(), hasSize(1)); + assertThat( + validationException.validationErrors().get(0), + containsString("field [a] is not of one of the expected types. The value [5] cannot be converted to one of [String, Boolean]") + ); + assertNull(map.get("a")); + + validationException = new ValidationException(); + result = ServiceUtils.removeAsOneOfTypes(map, "b", List.of(Boolean.class, Integer.class), validationException); + assertNull(result); + assertThat(validationException.validationErrors(), hasSize(1)); + assertThat( + validationException.validationErrors().get(0), + containsString( + "field [b] is not of one of the expected types. The value [a string] cannot be converted to one of [Boolean, Integer]" + ) + ); + assertNull(map.get("b")); + + validationException = new ValidationException(); + result = ServiceUtils.removeAsOneOfTypes(map, "c", List.of(String.class, Integer.class), validationException); + assertNull(result); + assertThat(validationException.validationErrors(), hasSize(1)); + assertThat( + validationException.validationErrors().get(0), + containsString( + "field [c] is not of one of the expected types. The value [true] cannot be converted to one of [String, Integer]" + ) + ); + assertNull(map.get("c")); + + validationException = new ValidationException(); + result = ServiceUtils.removeAsOneOfTypes(map, "d", List.of(String.class, Boolean.class), validationException); + assertNull(result); + assertThat(validationException.validationErrors(), hasSize(1)); + assertThat( + validationException.validationErrors().get(0), + containsString("field [d] is not of one of the expected types. The value [5.0] cannot be converted to one of [String, Boolean]") + ); + assertNull(map.get("d")); + + validationException = new ValidationException(); + result = ServiceUtils.removeAsOneOfTypes(map, "e", List.of(String.class, Boolean.class), validationException); + assertNull(result); + assertThat(validationException.validationErrors(), hasSize(1)); + assertThat( + validationException.validationErrors().get(0), + containsString("field [e] is not of one of the expected types. The value [5] cannot be converted to one of [String, Boolean]") + ); + assertNull(map.get("e")); + + assertThat(map.entrySet(), empty()); + } + + public void testRemoveAsOneOfTypesMissingReturnsNull() { + Map map = new HashMap<>(Map.of("a", 5, "b", "a string", "c", Boolean.TRUE)); + assertNull(ServiceUtils.removeAsOneOfTypes(map, "missing", List.of(Integer.class), new ValidationException())); + assertThat(map.entrySet(), hasSize(3)); + } + public void testConvertToUri_CreatesUri() { var validation = new ValidationException(); var uri = convertToUri("www.elastic.co", "name", "scope", validation); @@ -347,6 +438,22 @@ public void testExtractOptionalPositiveInt() { assertThat(validation.validationErrors(), hasSize(1)); } + public void testExtractOptionalPositiveLong_IntegerValue() { + var validation = new ValidationException(); + validation.addValidationError("previous error"); + Map map = modifiableMap(Map.of("abc", 3)); + assertEquals(Long.valueOf(3), extractOptionalPositiveLong(map, "abc", "scope", validation)); + assertThat(validation.validationErrors(), hasSize(1)); + } + + public void testExtractOptionalPositiveLong() { + var validation = new ValidationException(); + validation.addValidationError("previous error"); + Map map = modifiableMap(Map.of("abc", 4_000_000_000L)); + assertEquals(Long.valueOf(4_000_000_000L), extractOptionalPositiveLong(map, "abc", "scope", validation)); + assertThat(validation.validationErrors(), hasSize(1)); + } + public void testExtractOptionalEnum_ReturnsNull_WhenFieldDoesNotExist() { var validation = new ValidationException(); Map map = modifiableMap(Map.of("key", "value")); @@ -470,6 +577,127 @@ public void testExtractOptionalTimeValue_ReturnsNullAndAddsException_WhenTimeVal ); } + public void testExtractOptionalDouble_ExtractsAsDoubleInRange() { + var validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", 1.01)); + var result = ServiceUtils.extractOptionalDoubleInRange(map, "key", 0.0, 2.0, "test_scope", validationException); + assertEquals(Double.valueOf(1.01), result); + assertTrue(map.isEmpty()); + assertThat(validationException.validationErrors().size(), is(0)); + } + + public void testExtractOptionalDouble_InRange_ReturnsNullWhenKeyNotPresent() { + var validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", 1.01)); + var result = ServiceUtils.extractOptionalDoubleInRange(map, "other_key", 0.0, 2.0, "test_scope", validationException); + assertNull(result); + assertThat(map.size(), is(1)); + assertThat(map.get("key"), is(1.01)); + } + + public void testExtractOptionalDouble_InRange_HasErrorWhenBelowMinValue() { + var validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", -2.0)); + var result = ServiceUtils.extractOptionalDoubleInRange(map, "key", 0.0, 2.0, "test_scope", validationException); + assertNull(result); + assertThat(validationException.validationErrors().size(), is(1)); + assertThat( + validationException.validationErrors().get(0), + is("[test_scope] Invalid value [-2.0]. [key] must be a greater than or equal to [0.0]") + ); + } + + public void testExtractOptionalDouble_InRange_HasErrorWhenAboveMaxValue() { + var validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", 12.0)); + var result = ServiceUtils.extractOptionalDoubleInRange(map, "key", 0.0, 2.0, "test_scope", validationException); + assertNull(result); + assertThat(validationException.validationErrors().size(), is(1)); + assertThat( + validationException.validationErrors().get(0), + is("[test_scope] Invalid value [12.0]. [key] must be a less than or equal to [2.0]") + ); + } + + public void testExtractOptionalDouble_InRange_DoesNotCheckMinWhenNull() { + var validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", -2.0)); + var result = ServiceUtils.extractOptionalDoubleInRange(map, "key", null, 2.0, "test_scope", validationException); + assertEquals(Double.valueOf(-2.0), result); + assertTrue(map.isEmpty()); + assertThat(validationException.validationErrors().size(), is(0)); + } + + public void testExtractOptionalDouble_InRange_DoesNotCheckMaxWhenNull() { + var validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", 12.0)); + var result = ServiceUtils.extractOptionalDoubleInRange(map, "key", 0.0, null, "test_scope", validationException); + assertEquals(Double.valueOf(12.0), result); + assertTrue(map.isEmpty()); + assertThat(validationException.validationErrors().size(), is(0)); + } + + public void testExtractOptionalFloat_ExtractsAFloat() { + Map map = modifiableMap(Map.of("key", 1.0f)); + var result = ServiceUtils.extractOptionalFloat(map, "key"); + assertThat(result, is(1.0f)); + assertTrue(map.isEmpty()); + } + + public void testExtractOptionalFloat_ReturnsNullWhenKeyNotPresent() { + Map map = modifiableMap(Map.of("key", 1.0f)); + var result = ServiceUtils.extractOptionalFloat(map, "other_key"); + assertNull(result); + assertThat(map.size(), is(1)); + assertThat(map.get("key"), is(1.0f)); + } + + public void testExtractRequiredEnum_ExtractsAEnum() { + ValidationException validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", "ingest")); + var result = ServiceUtils.extractRequiredEnum( + map, + "key", + "testscope", + InputType::fromString, + EnumSet.allOf(InputType.class), + validationException + ); + assertThat(result, is(InputType.INGEST)); + } + + public void testExtractRequiredEnum_ReturnsNullWhenEnumValueIsNotPresent() { + ValidationException validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", "invalid")); + var result = ServiceUtils.extractRequiredEnum( + map, + "key", + "testscope", + InputType::fromString, + EnumSet.allOf(InputType.class), + validationException + ); + assertNull(result); + assertThat(validationException.validationErrors().size(), is(1)); + assertThat(validationException.validationErrors().get(0), containsString("Invalid value [invalid] received. [key] must be one of")); + } + + public void testExtractRequiredEnum_HasValidationErrorOnMissingSetting() { + ValidationException validationException = new ValidationException(); + Map map = modifiableMap(Map.of("key", "ingest")); + var result = ServiceUtils.extractRequiredEnum( + map, + "missing_key", + "testscope", + InputType::fromString, + EnumSet.allOf(InputType.class), + validationException + ); + assertNull(result); + assertThat(validationException.validationErrors().size(), is(1)); + assertThat(validationException.validationErrors().get(0), is("[testscope] does not contain the required setting [missing_key]")); + } + public void testGetEmbeddingSize_ReturnsError_WhenTextEmbeddingResults_IsEmpty() { var service = mock(InferenceService.class); diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioServiceTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioServiceTests.java new file mode 100644 index 0000000000000..51593c8d052d9 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/AzureAiStudioServiceTests.java @@ -0,0 +1,1177 @@ +/* + * 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.azureaistudio; + +import org.apache.http.HttpHeaders; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.inference.ChunkedInferenceServiceResults; +import org.elasticsearch.inference.ChunkingOptions; +import org.elasticsearch.inference.InferenceServiceResults; +import org.elasticsearch.inference.InputType; +import org.elasticsearch.inference.Model; +import org.elasticsearch.inference.ModelConfigurations; +import org.elasticsearch.inference.ModelSecrets; +import org.elasticsearch.inference.SimilarityMeasure; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.http.MockResponse; +import org.elasticsearch.test.http.MockWebServer; +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.ChatCompletionResults; +import org.elasticsearch.xpack.core.inference.results.ChunkedTextEmbeddingResults; +import org.elasticsearch.xpack.core.ml.inference.results.ChunkedNlpInferenceResults; +import org.elasticsearch.xpack.inference.external.http.HttpClientManager; +import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSender; +import org.elasticsearch.xpack.inference.external.http.sender.HttpRequestSenderTests; +import org.elasticsearch.xpack.inference.external.http.sender.Sender; +import org.elasticsearch.xpack.inference.logging.ThrottlerManager; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionModelTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionServiceSettingsTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionTaskSettings; +import org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionTaskSettingsTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModel; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsModelTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsServiceSettingsTests; +import org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsTaskSettingsTests; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.elasticsearch.xpack.inference.Utils.inferenceUtilityPool; +import static org.elasticsearch.xpack.inference.Utils.mockClusterServiceEmpty; +import static org.elasticsearch.xpack.inference.external.http.Utils.entityAsMap; +import static org.elasticsearch.xpack.inference.external.http.Utils.getUrl; +import static org.elasticsearch.xpack.inference.external.request.azureopenai.AzureOpenAiUtils.API_KEY_HEADER; +import static org.elasticsearch.xpack.inference.results.ChunkedTextEmbeddingResultsTests.asMapWithListsInsteadOfArrays; +import static org.elasticsearch.xpack.inference.services.ServiceComponentsTests.createWithEmptySettings; +import static org.elasticsearch.xpack.inference.services.Utils.getInvalidModel; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.API_KEY_FIELD; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class AzureAiStudioServiceTests extends ESTestCase { + private static final TimeValue TIMEOUT = new TimeValue(30, TimeUnit.SECONDS); + private final MockWebServer webServer = new MockWebServer(); + private ThreadPool threadPool; + private HttpClientManager clientManager; + + @Before + public void init() throws Exception { + webServer.start(); + threadPool = createThreadPool(inferenceUtilityPool()); + clientManager = HttpClientManager.create(Settings.EMPTY, threadPool, mockClusterServiceEmpty(), mock(ThrottlerManager.class)); + } + + @After + public void shutdown() throws IOException { + clientManager.close(); + terminate(threadPool); + webServer.close(); + } + + public void testParseRequestConfig_CreatesAnAzureAiStudioEmbeddingsModel() throws IOException { + try (var service = createService()) { + ActionListener modelVerificationListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + + var embeddingsModel = (AzureAiStudioEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().target(), is("http://target.local")); + assertThat(embeddingsModel.getServiceSettings().provider(), is(AzureAiStudioProvider.OPENAI)); + assertThat(embeddingsModel.getServiceSettings().endpointType(), is(AzureAiStudioEndpointType.TOKEN)); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + }, exception -> fail("Unexpected exception: " + exception)); + + service.parseRequestConfig( + "id", + TaskType.TEXT_EMBEDDING, + getRequestConfigMap( + getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", null, null, null, null), + getEmbeddingsTaskSettingsMap("user"), + getSecretSettingsMap("secret") + ), + Set.of(), + modelVerificationListener + ); + } + } + + public void testParseRequestConfig_CreatesAnAzureAiStudioChatCompletionModel() throws IOException { + try (var service = createService()) { + ActionListener modelVerificationListener = ActionListener.wrap(model -> { + assertThat(model, instanceOf(AzureAiStudioChatCompletionModel.class)); + + var completionModel = (AzureAiStudioChatCompletionModel) model; + assertThat(completionModel.getServiceSettings().target(), is("http://target.local")); + assertThat(completionModel.getServiceSettings().provider(), is(AzureAiStudioProvider.OPENAI)); + assertThat(completionModel.getServiceSettings().endpointType(), is(AzureAiStudioEndpointType.TOKEN)); + assertThat(completionModel.getSecretSettings().apiKey().toString(), is("secret")); + assertNull(completionModel.getTaskSettings().temperature()); + assertTrue(completionModel.getTaskSettings().doSample()); + }, exception -> fail("Unexpected exception: " + exception)); + + service.parseRequestConfig( + "id", + TaskType.COMPLETION, + getRequestConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(null, null, true, null), + getSecretSettingsMap("secret") + ), + Set.of(), + modelVerificationListener + ); + } + } + + public void testParseRequestConfig_ThrowsUnsupportedModelType() throws IOException { + try (var service = createService()) { + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat(exception.getMessage(), is("The [azureaistudio] service does not support task type [sparse_embedding]")); + } + ); + + service.parseRequestConfig( + "id", + TaskType.SPARSE_EMBEDDING, + getRequestConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(null, null, true, null), + getSecretSettingsMap("secret") + ), + Set.of(), + modelVerificationListener + ); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInConfig() throws IOException { + try (var service = createService()) { + var config = getRequestConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(null, null, true, null), + getSecretSettingsMap("secret") + ); + config.put("extra_key", "value"); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.COMPLETION, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInEmbeddingServiceSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", null, null, null, null); + serviceSettings.put("extra_key", "value"); + + var config = getRequestConfigMap(serviceSettings, getEmbeddingsTaskSettingsMap("user"), getSecretSettingsMap("secret")); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenDimsSetByUserExistsInEmbeddingServiceSettingsMap() throws IOException { + try (var service = createService()) { + var config = getRequestConfigMap( + getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, null, null), + getEmbeddingsTaskSettingsMap("user"), + getSecretSettingsMap("secret") + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ValidationException.class)); + assertThat( + exception.getMessage(), + containsString("[service_settings] does not allow the setting [dimensions_set_by_user]") + ); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInEmbeddingTaskSettingsMap() throws IOException { + try (var service = createService()) { + var taskSettings = getEmbeddingsTaskSettingsMap("user"); + taskSettings.put("extra_key", "value"); + + var config = getRequestConfigMap( + getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", null, null, null, null), + taskSettings, + getSecretSettingsMap("secret") + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInEmbeddingSecretSettingsMap() throws IOException { + try (var service = createService()) { + var secretSettings = getSecretSettingsMap("secret"); + secretSettings.put("extra_key", "value"); + + var config = getRequestConfigMap( + getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", null, null, null, null), + getEmbeddingsTaskSettingsMap("user"), + secretSettings + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInChatCompletionServiceSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"); + serviceSettings.put("extra_key", "value"); + + var config = getRequestConfigMap( + serviceSettings, + getChatCompletionTaskSettingsMap(null, 2.0, null, null), + getSecretSettingsMap("secret") + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.COMPLETION, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInChatCompletionTaskSettingsMap() throws IOException { + try (var service = createService()) { + var taskSettings = getChatCompletionTaskSettingsMap(null, 2.0, null, null); + taskSettings.put("extra_key", "value"); + + var config = getRequestConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + taskSettings, + getSecretSettingsMap("secret") + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.COMPLETION, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenAnExtraKeyExistsInChatCompletionSecretSettingsMap() throws IOException { + try (var service = createService()) { + var secretSettings = getSecretSettingsMap("secret"); + secretSettings.put("extra_key", "value"); + + var config = getRequestConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(null, 2.0, null, null), + secretSettings + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("Model configuration contains settings [{extra_key=value}] unknown to the [azureaistudio] service") + ); + } + ); + + service.parseRequestConfig("id", TaskType.COMPLETION, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenProviderIsNotValidForEmbeddings() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "databricks", "token", null, null, null, null); + + var config = getRequestConfigMap(serviceSettings, getEmbeddingsTaskSettingsMap("user"), getSecretSettingsMap("secret")); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat(exception.getMessage(), is("The [text_embedding] task type for provider [databricks] is not available")); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenEndpointTypeIsNotValidForEmbeddingsProvider() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "openai", "realtime", null, null, null, null); + + var config = getRequestConfigMap(serviceSettings, getEmbeddingsTaskSettingsMap("user"), getSecretSettingsMap("secret")); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("The [realtime] endpoint type with [text_embedding] task type for provider [openai] is not available") + ); + } + ); + + service.parseRequestConfig("id", TaskType.TEXT_EMBEDDING, config, Set.of(), modelVerificationListener); + } + } + + public void testParseRequestConfig_ThrowsWhenEndpointTypeIsNotValidForChatCompletionProvider() throws IOException { + try (var service = createService()) { + var serviceSettings = getChatCompletionServiceSettingsMap("http://target.local", "openai", "realtime"); + + var config = getRequestConfigMap( + serviceSettings, + getChatCompletionTaskSettingsMap(null, null, null, null), + getSecretSettingsMap("secret") + ); + + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat( + exception.getMessage(), + is("The [realtime] endpoint type with [completion] task type for provider [openai] is not available") + ); + } + ); + + service.parseRequestConfig("id", TaskType.COMPLETION, config, Set.of(), modelVerificationListener); + } + } + + public void testParsePersistedConfig_CreatesAnAzureAiStudioEmbeddingsModel() throws IOException { + try (var service = createService()) { + var config = getPersistedConfigMap( + getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, 512, null), + getEmbeddingsTaskSettingsMap("user"), + getSecretSettingsMap("secret") + ); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.TEXT_EMBEDDING, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + + var embeddingsModel = (AzureAiStudioEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().target(), is("http://target.local")); + assertThat(embeddingsModel.getServiceSettings().provider(), is(AzureAiStudioProvider.OPENAI)); + assertThat(embeddingsModel.getServiceSettings().endpointType(), is(AzureAiStudioEndpointType.TOKEN)); + assertThat(embeddingsModel.getServiceSettings().dimensions(), is(1024)); + assertThat(embeddingsModel.getServiceSettings().dimensionsSetByUser(), is(true)); + assertThat(embeddingsModel.getServiceSettings().maxInputTokens(), is(512)); + assertThat(embeddingsModel.getSecretSettings().apiKey().toString(), is("secret")); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + } + } + + public void testParsePersistedConfig_CreatesAnAzureAiStudioChatCompletionModel() throws IOException { + try (var service = createService()) { + var config = getPersistedConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(1.0, 2.0, true, 512), + getSecretSettingsMap("secret") + ); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.COMPLETION, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioChatCompletionModel.class)); + + var chatCompletionModel = (AzureAiStudioChatCompletionModel) model; + assertThat(chatCompletionModel.getServiceSettings().target(), is("http://target.local")); + assertThat(chatCompletionModel.getServiceSettings().provider(), is(AzureAiStudioProvider.OPENAI)); + assertThat(chatCompletionModel.getServiceSettings().endpointType(), is(AzureAiStudioEndpointType.TOKEN)); + assertThat(chatCompletionModel.getTaskSettings().temperature(), is(1.0)); + assertThat(chatCompletionModel.getTaskSettings().topP(), is(2.0)); + assertThat(chatCompletionModel.getTaskSettings().doSample(), is(true)); + assertThat(chatCompletionModel.getTaskSettings().maxNewTokens(), is(512)); + } + } + + public void testParsePersistedConfig_ThrowsUnsupportedModelType() throws IOException { + try (var service = createService()) { + ActionListener modelVerificationListener = ActionListener.wrap( + model -> fail("Expected exception, but got model: " + model), + exception -> { + assertThat(exception, instanceOf(ElasticsearchStatusException.class)); + assertThat(exception.getMessage(), is("The [azureaistudio] service does not support task type [sparse_embedding]")); + } + ); + + service.parseRequestConfig( + "id", + TaskType.SPARSE_EMBEDDING, + getRequestConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(null, null, true, null), + getSecretSettingsMap("secret") + ), + Set.of(), + modelVerificationListener + ); + } + } + + public void testParsePersistedConfigWithSecrets_ThrowsErrorTryingToParseInvalidModel() throws IOException { + try (var service = createService()) { + var config = getPersistedConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(1.0, 2.0, true, 512), + getSecretSettingsMap("secret") + ); + + var thrownException = expectThrows( + ElasticsearchStatusException.class, + () -> service.parsePersistedConfigWithSecrets("id", TaskType.SPARSE_EMBEDDING, config.config(), config.secrets()) + ); + + assertThat( + thrownException.getMessage(), + is("Failed to parse stored model [id] for [azureaistudio] service, please delete and add the service again") + ); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenAnExtraKeyExistsInConfig() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, 512, null); + var taskSettings = getEmbeddingsTaskSettingsMap("user"); + var secretSettings = getSecretSettingsMap("secret"); + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + config.config().put("extra_key", "value"); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.TEXT_EMBEDDING, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenExtraKeyExistsInEmbeddingServiceSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, 512, null); + serviceSettings.put("extra_key", "value"); + + var taskSettings = getEmbeddingsTaskSettingsMap("user"); + var secretSettings = getSecretSettingsMap("secret"); + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.TEXT_EMBEDDING, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenAnExtraKeyExistsInEmbeddingTaskSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, 512, null); + var taskSettings = getEmbeddingsTaskSettingsMap("user"); + taskSettings.put("extra_key", "value"); + + var secretSettings = getSecretSettingsMap("secret"); + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.TEXT_EMBEDDING, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenAnExtraKeyExistsInEmbeddingSecretSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, 512, null); + var taskSettings = getEmbeddingsTaskSettingsMap("user"); + var secretSettings = getSecretSettingsMap("secret"); + secretSettings.put("extra_key", "value"); + + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.TEXT_EMBEDDING, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenAnExtraKeyExistsInChatCompletionServiceSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"); + serviceSettings.put("extra_key", "value"); + var taskSettings = getChatCompletionTaskSettingsMap(1.0, 2.0, true, 512); + var secretSettings = getSecretSettingsMap("secret"); + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.COMPLETION, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioChatCompletionModel.class)); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenAnExtraKeyExistsInChatCompletionTaskSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"); + var taskSettings = getChatCompletionTaskSettingsMap(1.0, 2.0, true, 512); + taskSettings.put("extra_key", "value"); + var secretSettings = getSecretSettingsMap("secret"); + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.COMPLETION, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioChatCompletionModel.class)); + } + } + + public void testParsePersistedConfig_DoesNotThrowWhenAnExtraKeyExistsInChatCompletionSecretSettingsMap() throws IOException { + try (var service = createService()) { + var serviceSettings = getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"); + var taskSettings = getChatCompletionTaskSettingsMap(1.0, 2.0, true, 512); + var secretSettings = getSecretSettingsMap("secret"); + secretSettings.put("extra_key", "value"); + var config = getPersistedConfigMap(serviceSettings, taskSettings, secretSettings); + + var model = service.parsePersistedConfigWithSecrets("id", TaskType.COMPLETION, config.config(), config.secrets()); + + assertThat(model, instanceOf(AzureAiStudioChatCompletionModel.class)); + } + } + + public void testParsePersistedConfig_WithoutSecretsCreatesEmbeddingsModel() throws IOException { + try (var service = createService()) { + var config = getPersistedConfigMap( + getEmbeddingsServiceSettingsMap("http://target.local", "openai", "token", 1024, true, 512, null), + getEmbeddingsTaskSettingsMap("user"), + Map.of() + ); + + var model = service.parsePersistedConfig("id", TaskType.TEXT_EMBEDDING, config.config()); + + assertThat(model, instanceOf(AzureAiStudioEmbeddingsModel.class)); + + var embeddingsModel = (AzureAiStudioEmbeddingsModel) model; + assertThat(embeddingsModel.getServiceSettings().target(), is("http://target.local")); + assertThat(embeddingsModel.getServiceSettings().provider(), is(AzureAiStudioProvider.OPENAI)); + assertThat(embeddingsModel.getServiceSettings().endpointType(), is(AzureAiStudioEndpointType.TOKEN)); + assertThat(embeddingsModel.getServiceSettings().dimensions(), is(1024)); + assertThat(embeddingsModel.getServiceSettings().dimensionsSetByUser(), is(true)); + assertThat(embeddingsModel.getServiceSettings().maxInputTokens(), is(512)); + assertThat(embeddingsModel.getTaskSettings().user(), is("user")); + } + } + + public void testParsePersistedConfig_WithoutSecretsCreatesChatCompletionModel() throws IOException { + try (var service = createService()) { + var config = getPersistedConfigMap( + getChatCompletionServiceSettingsMap("http://target.local", "openai", "token"), + getChatCompletionTaskSettingsMap(1.0, 2.0, true, 512), + Map.of() + ); + + var model = service.parsePersistedConfig("id", TaskType.COMPLETION, config.config()); + + assertThat(model, instanceOf(AzureAiStudioChatCompletionModel.class)); + + var chatCompletionModel = (AzureAiStudioChatCompletionModel) model; + assertThat(chatCompletionModel.getServiceSettings().target(), is("http://target.local")); + assertThat(chatCompletionModel.getServiceSettings().provider(), is(AzureAiStudioProvider.OPENAI)); + assertThat(chatCompletionModel.getServiceSettings().endpointType(), is(AzureAiStudioEndpointType.TOKEN)); + assertThat(chatCompletionModel.getTaskSettings().temperature(), is(1.0)); + assertThat(chatCompletionModel.getTaskSettings().topP(), is(2.0)); + assertThat(chatCompletionModel.getTaskSettings().doSample(), is(true)); + assertThat(chatCompletionModel.getTaskSettings().maxNewTokens(), is(512)); + } + } + + public void testCheckModelConfig_ForEmbeddingsModel_Works() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testEmbeddingResultJson)); + + var model = AzureAiStudioEmbeddingsModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + false, + null, + null, + null, + null + ); + + PlainActionFuture listener = new PlainActionFuture<>(); + service.checkModelConfig(model, listener); + + var result = listener.actionGet(TIMEOUT); + assertThat( + result, + is( + AzureAiStudioEmbeddingsModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 2, + false, + null, + SimilarityMeasure.DOT_PRODUCT, + null, + null + ) + ) + ); + + assertThat(webServer.requests(), hasSize(1)); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + MatcherAssert.assertThat(requestMap, Matchers.is(Map.of("input", List.of("how big")))); + } + } + + public void testCheckModelConfig_ForEmbeddingsModel_ThrowsIfEmbeddingSizeDoesNotMatchValueSetByUser() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testEmbeddingResultJson)); + + var model = AzureAiStudioEmbeddingsModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 3, + true, + null, + null, + null, + null + ); + + PlainActionFuture listener = new PlainActionFuture<>(); + service.checkModelConfig(model, listener); + + var exception = expectThrows(ElasticsearchStatusException.class, () -> listener.actionGet(TIMEOUT)); + assertThat( + exception.getMessage(), + is( + "The retrieved embeddings size [2] does not match the size specified in the settings [3]. " + + "Please recreate the [id] configuration with the correct dimensions" + ) + ); + + assertThat(webServer.requests(), hasSize(1)); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + MatcherAssert.assertThat(requestMap, Matchers.is(Map.of("input", List.of("how big"), "dimensions", 3))); + } + } + + public void testCheckModelConfig_WorksForChatCompletionsModel() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testChatCompletionResultJson)); + + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + null, + null, + null + ); + + PlainActionFuture listener = new PlainActionFuture<>(); + service.checkModelConfig(model, listener); + + var result = listener.actionGet(TIMEOUT); + assertThat( + result, + is( + AzureAiStudioChatCompletionModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + null, + AzureAiStudioChatCompletionTaskSettings.DEFAULT_MAX_NEW_TOKENS, + null + ) + ) + ); + } + } + + public void testInfer_ThrowsErrorWhenModelIsNotAzureAiStudioModel() throws IOException { + var sender = mock(Sender.class); + + var factory = mock(HttpRequestSender.Factory.class); + when(factory.createSender(anyString())).thenReturn(sender); + + var mockModel = getInvalidModel("model_id", "service_name"); + + try (var service = new AzureAiStudioService(factory, createWithEmptySettings(threadPool))) { + PlainActionFuture listener = new PlainActionFuture<>(); + service.infer( + mockModel, + null, + List.of(""), + new HashMap<>(), + InputType.INGEST, + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ); + + var thrownException = expectThrows(ElasticsearchStatusException.class, () -> listener.actionGet(TIMEOUT)); + assertThat( + thrownException.getMessage(), + is("The internal model was invalid, please delete the service [service_name] with id [model_id] and add it again.") + ); + + verify(factory, times(1)).createSender(anyString()); + verify(sender, times(1)).start(); + } + + verify(sender, times(1)).close(); + verifyNoMoreInteractions(factory); + verifyNoMoreInteractions(sender); + } + + public void testChunkedInfer_Embeddings_CallsInfer_ConvertsFloatResponse() throws IOException, URISyntaxException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + + String responseJson = """ + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.0123, + -0.0123 + ] + } + ], + "model": "text-embedding-ada-002-v2", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + """; + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(responseJson)); + + var model = AzureAiStudioEmbeddingsModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + false, + null, + null, + "user", + null + ); + PlainActionFuture> listener = new PlainActionFuture<>(); + service.chunkedInfer( + model, + List.of("abc"), + new HashMap<>(), + InputType.INGEST, + new ChunkingOptions(null, null), + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ); + + var result = listener.actionGet(TIMEOUT).get(0); + assertThat(result, CoreMatchers.instanceOf(ChunkedTextEmbeddingResults.class)); + + assertThat( + asMapWithListsInsteadOfArrays((ChunkedTextEmbeddingResults) result), + Matchers.is( + Map.of( + ChunkedTextEmbeddingResults.FIELD_NAME, + List.of( + Map.of( + ChunkedNlpInferenceResults.TEXT, + "abc", + ChunkedNlpInferenceResults.INFERENCE, + List.of((double) 0.0123f, (double) -0.0123f) + ) + ) + ) + ) + ); + 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())); + assertThat(webServer.requests().get(0).getHeader(API_KEY_HEADER), equalTo("apikey")); + + var requestMap = entityAsMap(webServer.requests().get(0).getBody()); + assertThat(requestMap.size(), Matchers.is(2)); + assertThat(requestMap.get("input"), Matchers.is(List.of("abc"))); + assertThat(requestMap.get("user"), Matchers.is("user")); + } + } + + public void testInfer_ThrowsWhenQueryIsPresent() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testChatCompletionResultJson)); + + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey" + ); + + PlainActionFuture listener = new PlainActionFuture<>(); + UnsupportedOperationException exception = expectThrows( + UnsupportedOperationException.class, + () -> service.infer( + model, + "should throw", + List.of("abc"), + new HashMap<>(), + InputType.INGEST, + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ) + ); + + assertThat(exception.getMessage(), is("Azure AI Studio service does not support inference with query input")); + } + } + + public void testInfer_WithChatCompletionModel() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + webServer.enqueue(new MockResponse().setResponseCode(200).setBody(testChatCompletionResultJson)); + + var model = AzureAiStudioChatCompletionModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey" + ); + + PlainActionFuture listener = new PlainActionFuture<>(); + service.infer( + model, + null, + List.of("abc"), + new HashMap<>(), + InputType.INGEST, + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ); + + var result = listener.actionGet(TIMEOUT); + assertThat(result, CoreMatchers.instanceOf(ChatCompletionResults.class)); + + var completionResults = (ChatCompletionResults) result; + assertThat(completionResults.getResults().size(), is(1)); + assertThat(completionResults.getResults().get(0).content(), is("test completion content")); + } + } + + public void testInfer_UnauthorisedResponse() throws IOException { + var senderFactory = HttpRequestSenderTests.createSenderFactory(threadPool, clientManager); + + try (var service = new AzureAiStudioService(senderFactory, createWithEmptySettings(threadPool))) { + + String responseJson = """ + { + "error": { + "message": "Incorrect API key provided:", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } + } + """; + webServer.enqueue(new MockResponse().setResponseCode(401).setBody(responseJson)); + + var model = AzureAiStudioEmbeddingsModelTests.createModel( + "id", + getUrl(webServer), + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + false, + null, + null, + "user", + null + ); + PlainActionFuture listener = new PlainActionFuture<>(); + service.infer( + model, + null, + List.of("abc"), + new HashMap<>(), + InputType.INGEST, + InferenceAction.Request.DEFAULT_TIMEOUT, + listener + ); + + var error = expectThrows(ElasticsearchException.class, () -> listener.actionGet(TIMEOUT)); + assertThat(error.getMessage(), containsString("Received an authentication error status code for request")); + assertThat(error.getMessage(), containsString("Error message: [Incorrect API key provided:]")); + assertThat(webServer.requests(), hasSize(1)); + } + } + + // ---------------------------------------------------------------- + + private AzureAiStudioService createService() { + return new AzureAiStudioService(mock(HttpRequestSender.Factory.class), createWithEmptySettings(threadPool)); + } + + private Map getRequestConfigMap( + Map serviceSettings, + Map taskSettings, + Map secretSettings + ) { + var builtServiceSettings = new HashMap<>(); + builtServiceSettings.putAll(serviceSettings); + builtServiceSettings.putAll(secretSettings); + + return new HashMap<>( + Map.of(ModelConfigurations.SERVICE_SETTINGS, builtServiceSettings, ModelConfigurations.TASK_SETTINGS, taskSettings) + ); + } + + private record PeristedConfigRecord(Map config, Map secrets) {} + + private PeristedConfigRecord getPersistedConfigMap( + Map serviceSettings, + Map taskSettings, + Map secretSettings + ) { + + return new PeristedConfigRecord( + new HashMap<>(Map.of(ModelConfigurations.SERVICE_SETTINGS, serviceSettings, ModelConfigurations.TASK_SETTINGS, taskSettings)), + new HashMap<>(Map.of(ModelSecrets.SECRET_SETTINGS, secretSettings)) + ); + } + + private PeristedConfigRecord getPersistedConfigMap(Map serviceSettings, Map taskSettings) { + + return new PeristedConfigRecord( + new HashMap<>(Map.of(ModelConfigurations.SERVICE_SETTINGS, serviceSettings, ModelConfigurations.TASK_SETTINGS, taskSettings)), + null + ); + } + + private static Map getEmbeddingsServiceSettingsMap( + String target, + String provider, + String endpointType, + @Nullable Integer dimensions, + @Nullable Boolean dimensionsSetByUser, + @Nullable Integer maxTokens, + @Nullable SimilarityMeasure similarityMeasure + ) { + return AzureAiStudioEmbeddingsServiceSettingsTests.createRequestSettingsMap( + target, + provider, + endpointType, + dimensions, + dimensionsSetByUser, + maxTokens, + similarityMeasure + ); + } + + private static Map getEmbeddingsTaskSettingsMap(@Nullable String user) { + return AzureAiStudioEmbeddingsTaskSettingsTests.getTaskSettingsMap(user); + } + + private static HashMap getChatCompletionServiceSettingsMap(String target, String provider, String endpointType) { + return AzureAiStudioChatCompletionServiceSettingsTests.createRequestSettingsMap(target, provider, endpointType); + } + + public static Map getChatCompletionTaskSettingsMap( + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens + ) { + return AzureAiStudioChatCompletionTaskSettingsTests.getTaskSettingsMap(temperature, topP, doSample, maxNewTokens); + } + + private static Map getSecretSettingsMap(String apiKey) { + return new HashMap<>(Map.of(API_KEY_FIELD, apiKey)); + } + + private static final String testEmbeddingResultJson = """ + { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [ + 0.0123, + -0.0123 + ] + } + ], + "model": "text-embedding-ada-002-v2", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + """; + + private static final String testChatCompletionResultJson = """ + { + "choices": [ + { + "finish_reason": "stop", + "index": 0, + "message": { + "content": "test completion content", + "role": "assistant", + "tool_calls": null + } + } + ], + "created": 1714006424, + "id": "f92b5b4d-0de3-4152-a3c6-5aae8a74555c", + "model": "", + "object": "chat.completion", + "usage": { + "completion_tokens": 35, + "prompt_tokens": 8, + "total_tokens": 43 + } + } + """; +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionModelTests.java new file mode 100644 index 0000000000000..bd34a34285cf2 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionModelTests.java @@ -0,0 +1,234 @@ +/* + * 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.azureaistudio.completion; + +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.net.URISyntaxException; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.completion.AzureAiStudioChatCompletionTaskSettingsTests.getTaskSettingsMap; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; + +public class AzureAiStudioChatCompletionModelTests extends ESTestCase { + + public void testOverrideWith_OverridesWithoutValues() { + var model = createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 1.0, + 2.0, + false, + 512, + null + ); + var requestTaskSettingsMap = getTaskSettingsMap(null, null, null, null); + var overriddenModel = AzureAiStudioChatCompletionModel.of(model, requestTaskSettingsMap); + + assertThat(overriddenModel, sameInstance(overriddenModel)); + } + + public void testOverrideWith_temperature() { + var model = createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 1.0, + null, + null, + null, + null + ); + var requestTaskSettings = getTaskSettingsMap(0.5, null, null, null); + var overriddenModel = AzureAiStudioChatCompletionModel.of(model, requestTaskSettings); + assertThat( + overriddenModel, + is( + createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + 0.5, + null, + null, + null, + null + ) + ) + ); + } + + public void testOverrideWith_topP() { + var model = createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + 0.8, + null, + null, + null + ); + var requestTaskSettings = getTaskSettingsMap(null, 0.5, null, null); + var overriddenModel = AzureAiStudioChatCompletionModel.of(model, requestTaskSettings); + assertThat( + overriddenModel, + is( + createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + 0.5, + null, + null, + null + ) + ) + ); + } + + public void testOverrideWith_doSample() { + var model = createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + true, + null, + null + ); + var requestTaskSettings = getTaskSettingsMap(null, null, false, null); + var overriddenModel = AzureAiStudioChatCompletionModel.of(model, requestTaskSettings); + assertThat( + overriddenModel, + is( + createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + false, + null, + null + ) + ) + ); + } + + public void testOverrideWith_maxNewTokens() { + var model = createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + null, + 512, + null + ); + var requestTaskSettings = getTaskSettingsMap(null, null, null, 128); + var overriddenModel = AzureAiStudioChatCompletionModel.of(model, requestTaskSettings); + assertThat( + overriddenModel, + is( + createModel( + "id", + "target", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + null, + null, + 128, + null + ) + ) + ); + } + + public void testSetsProperUrlForOpenAITokenModel() throws URISyntaxException { + var model = createModel("id", "http://testtarget.local", AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + assertThat(model.getEndpointUri().toString(), is("http://testtarget.local")); + } + + public void testSetsProperUrlForNonOpenAiTokenModel() throws URISyntaxException { + var model = createModel("id", "http://testtarget.local", AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + assertThat(model.getEndpointUri().toString(), is("http://testtarget.local/v1/chat/completions")); + } + + public void testSetsProperUrlForRealtimeEndpointModel() throws URISyntaxException { + var model = createModel( + "id", + "http://testtarget.local", + AzureAiStudioProvider.MISTRAL, + AzureAiStudioEndpointType.REALTIME, + "apikey" + ); + assertThat(model.getEndpointUri().toString(), is("http://testtarget.local")); + } + + public static AzureAiStudioChatCompletionModel createModel( + String id, + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey + ) { + return createModel(id, target, provider, endpointType, apiKey, null, null, null, null, null); + } + + public static AzureAiStudioChatCompletionModel createModel( + String id, + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey, + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens, + @Nullable RateLimitSettings rateLimitSettings + ) { + return new AzureAiStudioChatCompletionModel( + id, + TaskType.COMPLETION, + "azureaistudio", + new AzureAiStudioChatCompletionServiceSettings(target, provider, endpointType, rateLimitSettings), + new AzureAiStudioChatCompletionTaskSettings(temperature, topP, doSample, maxNewTokens), + new DefaultSecretSettings(new SecureString(apiKey.toCharArray())) + ); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionRequestTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionRequestTaskSettingsTests.java new file mode 100644 index 0000000000000..53c7cb6971f20 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionRequestTaskSettingsTests.java @@ -0,0 +1,108 @@ +/* + * 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.azureaistudio.completion; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.test.ESTestCase; +import org.hamcrest.MatcherAssert; + +import java.util.HashMap; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.DO_SAMPLE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.MAX_NEW_TOKENS_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TEMPERATURE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TOP_P_FIELD; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioChatCompletionRequestTaskSettingsTests extends ESTestCase { + public void testFromMap_ReturnsEmptySettings_WhenTheMapIsEmpty() { + var settings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of())); + assertThat(settings, is(AzureAiStudioChatCompletionRequestTaskSettings.EMPTY_SETTINGS)); + } + + public void testFromMap_ReturnsEmptySettings_WhenTheMapDoesNotContainTheFields() { + var settings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of("key", "model"))); + assertThat(settings, is(AzureAiStudioChatCompletionRequestTaskSettings.EMPTY_SETTINGS)); + } + + public void testFromMap_ReturnsTemperature() { + var settings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(TEMPERATURE_FIELD, 0.1))); + assertThat(settings.temperature(), is(0.1)); + } + + public void testFromMap_ReturnsTopP() { + var settings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(TOP_P_FIELD, 0.1))); + assertThat(settings.topP(), is(0.1)); + } + + public void testFromMap_ReturnsDoSample() { + var settings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(DO_SAMPLE_FIELD, true))); + assertThat(settings.doSample(), is(true)); + } + + public void testFromMap_ReturnsMaxNewTokens() { + var settings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(MAX_NEW_TOKENS_FIELD, 512))); + assertThat(settings.maxNewTokens(), is(512)); + } + + public void testFromMap_TemperatureIsInvalidValue_ThrowsValidationException() { + var thrownException = expectThrows( + ValidationException.class, + () -> AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(TEMPERATURE_FIELD, "invalid"))) + ); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString( + Strings.format("field [temperature] is not of the expected type. The value [invalid] cannot be converted to a [Double]") + ) + ); + } + + public void testFromMap_TopPIsInvalidValue_ThrowsValidationException() { + var thrownException = expectThrows( + ValidationException.class, + () -> AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(TOP_P_FIELD, "invalid"))) + ); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString( + Strings.format("field [top_p] is not of the expected type. The value [invalid] cannot be converted to a [Double]") + ) + ); + } + + public void testFromMap_DoSampleIsInvalidValue_ThrowsStatusException() { + var thrownException = expectThrows( + ValidationException.class, + () -> AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(DO_SAMPLE_FIELD, "invalid"))) + ); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString("field [do_sample] is not of the expected type. The value [invalid] cannot be converted to a [Boolean]") + ); + } + + public void testFromMap_MaxTokensIsInvalidValue_ThrowsStatusException() { + var thrownException = expectThrows( + ValidationException.class, + () -> AzureAiStudioChatCompletionRequestTaskSettings.fromMap(new HashMap<>(Map.of(MAX_NEW_TOKENS_FIELD, "invalid"))) + ); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString("field [max_new_tokens] is not of the expected type. The value [invalid] cannot be converted to a [Integer]") + ); + } + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionServiceSettingsTests.java new file mode 100644 index 0000000000000..79d6e384d7693 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionServiceSettingsTests.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.inference.services.azureaistudio.completion; + +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.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; +import org.hamcrest.CoreMatchers; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.ENDPOINT_TYPE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.PROVIDER_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TARGET_FIELD; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioChatCompletionServiceSettingsTests extends ESTestCase { + public void testFromMap_Request_CreatesSettingsCorrectly() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + + var serviceSettings = AzureAiStudioChatCompletionServiceSettings.fromMap( + createRequestSettingsMap(target, provider, endpointType), + ConfigurationParseContext.REQUEST + ); + + assertThat( + serviceSettings, + is(new AzureAiStudioChatCompletionServiceSettings(target, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, null)) + ); + } + + public void testFromMap_RequestWithRateLimit_CreatesSettingsCorrectly() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + + var settingsMap = createRequestSettingsMap(target, provider, endpointType); + settingsMap.put(RateLimitSettings.FIELD_NAME, new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, 3))); + + var serviceSettings = AzureAiStudioChatCompletionServiceSettings.fromMap(settingsMap, ConfigurationParseContext.REQUEST); + + assertThat( + serviceSettings, + is( + new AzureAiStudioChatCompletionServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + new RateLimitSettings(3) + ) + ) + ); + } + + public void testFromMap_Persistent_CreatesSettingsCorrectly() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + + var serviceSettings = AzureAiStudioChatCompletionServiceSettings.fromMap( + createRequestSettingsMap(target, provider, endpointType), + ConfigurationParseContext.PERSISTENT + ); + + assertThat( + serviceSettings, + is(new AzureAiStudioChatCompletionServiceSettings(target, AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, null)) + ); + } + + public void testToXContent_WritesAllValues() throws IOException { + var settings = new AzureAiStudioChatCompletionServiceSettings( + "target_value", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + new RateLimitSettings(3) + ); + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + settings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, CoreMatchers.is(""" + {"target":"target_value","provider":"openai","endpoint_type":"token",""" + """ + "rate_limit":{"requests_per_minute":3}}""")); + } + + public void testToFilteredXContent_WritesAllValues() throws IOException { + var settings = new AzureAiStudioChatCompletionServiceSettings( + "target_value", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + new RateLimitSettings(3) + ); + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + var filteredXContent = settings.getFilteredXContentObject(); + filteredXContent.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, CoreMatchers.is(""" + {"target":"target_value","provider":"openai","endpoint_type":"token"}""")); + } + + public static HashMap createRequestSettingsMap(String target, String provider, String endpointType) { + return new HashMap<>(Map.of(TARGET_FIELD, target, PROVIDER_FIELD, provider, ENDPOINT_TYPE_FIELD, endpointType)); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionTaskSettingsTests.java new file mode 100644 index 0000000000000..bc541bbcf5369 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/completion/AzureAiStudioChatCompletionTaskSettingsTests.java @@ -0,0 +1,186 @@ +/* + * 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.azureaistudio.completion; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentType; +import org.hamcrest.MatcherAssert; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.DO_SAMPLE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.MAX_NEW_TOKENS_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TEMPERATURE_FIELD; +import static org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants.TOP_P_FIELD; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioChatCompletionTaskSettingsTests extends ESTestCase { + + public void testFromMap_AllValues() { + var taskMap = getTaskSettingsMap(1.0, 2.0, true, 512); + assertEquals( + new AzureAiStudioChatCompletionTaskSettings(1.0, 2.0, true, 512), + AzureAiStudioChatCompletionTaskSettings.fromMap(taskMap) + ); + } + + public void testFromMap_TemperatureIsInvalidValue_ThrowsValidationException() { + var taskMap = getTaskSettingsMap(null, 2.0, true, 512); + taskMap.put(TEMPERATURE_FIELD, "invalid"); + + var thrownException = expectThrows(ValidationException.class, () -> AzureAiStudioChatCompletionTaskSettings.fromMap(taskMap)); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString( + Strings.format("field [temperature] is not of the expected type. The value [invalid] cannot be converted to a [Double]") + ) + ); + } + + public void testFromMap_TopPIsInvalidValue_ThrowsValidationException() { + var taskMap = getTaskSettingsMap(null, 2.0, true, 512); + taskMap.put(TOP_P_FIELD, "invalid"); + + var thrownException = expectThrows(ValidationException.class, () -> AzureAiStudioChatCompletionTaskSettings.fromMap(taskMap)); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString( + Strings.format("field [top_p] is not of the expected type. The value [invalid] cannot be converted to a [Double]") + ) + ); + } + + public void testFromMap_DoSampleIsInvalidValue_ThrowsValidationException() { + var taskMap = getTaskSettingsMap(null, 2.0, true, 512); + taskMap.put(DO_SAMPLE_FIELD, "invalid"); + + var thrownException = expectThrows(ValidationException.class, () -> AzureAiStudioChatCompletionTaskSettings.fromMap(taskMap)); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString("field [do_sample] is not of the expected type. The value [invalid] cannot be converted to a [Boolean]") + ); + } + + public void testFromMap_MaxNewTokensIsInvalidValue_ThrowsValidationException() { + var taskMap = getTaskSettingsMap(null, 2.0, true, 512); + taskMap.put(MAX_NEW_TOKENS_FIELD, "invalid"); + + var thrownException = expectThrows(ValidationException.class, () -> AzureAiStudioChatCompletionTaskSettings.fromMap(taskMap)); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString( + Strings.format("field [max_new_tokens] is not of the expected type. The value [invalid] cannot be converted to a [Integer]") + ) + ); + } + + public void testFromMap_WithNoValues_DoesNotThrowException() { + var taskMap = AzureAiStudioChatCompletionTaskSettings.fromMap(new HashMap(Map.of())); + assertNull(taskMap.temperature()); + assertNull(taskMap.topP()); + assertNull(taskMap.doSample()); + assertNull(taskMap.maxNewTokens()); + } + + public void testOverrideWith_KeepsOriginalValuesWithOverridesAreNull() { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(1.0, 2.0, true, 512)); + var overrideSettings = AzureAiStudioChatCompletionTaskSettings.of( + settings, + AzureAiStudioChatCompletionRequestTaskSettings.EMPTY_SETTINGS + ); + MatcherAssert.assertThat(overrideSettings, is(settings)); + } + + public void testOverrideWith_UsesTemperatureOverride() { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(1.0, 2.0, true, 512)); + var overrideSettings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(getTaskSettingsMap(1.5, null, null, null)); + var overriddenTaskSettings = AzureAiStudioChatCompletionTaskSettings.of(settings, overrideSettings); + MatcherAssert.assertThat(overriddenTaskSettings, is(new AzureAiStudioChatCompletionTaskSettings(1.5, 2.0, true, 512))); + } + + public void testOverrideWith_UsesTopPOverride() { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(1.0, 2.0, true, 512)); + var overrideSettings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(getTaskSettingsMap(null, 0.2, null, null)); + var overriddenTaskSettings = AzureAiStudioChatCompletionTaskSettings.of(settings, overrideSettings); + MatcherAssert.assertThat(overriddenTaskSettings, is(new AzureAiStudioChatCompletionTaskSettings(1.0, 0.2, true, 512))); + } + + public void testOverrideWith_UsesDoSampleOverride() { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(1.0, 2.0, true, 512)); + var overrideSettings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(getTaskSettingsMap(null, null, false, null)); + var overriddenTaskSettings = AzureAiStudioChatCompletionTaskSettings.of(settings, overrideSettings); + MatcherAssert.assertThat(overriddenTaskSettings, is(new AzureAiStudioChatCompletionTaskSettings(1.0, 2.0, false, 512))); + } + + public void testOverrideWith_UsesMaxNewTokensOverride() { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(1.0, 2.0, true, 512)); + var overrideSettings = AzureAiStudioChatCompletionRequestTaskSettings.fromMap(getTaskSettingsMap(null, null, null, 128)); + var overriddenTaskSettings = AzureAiStudioChatCompletionTaskSettings.of(settings, overrideSettings); + MatcherAssert.assertThat(overriddenTaskSettings, is(new AzureAiStudioChatCompletionTaskSettings(1.0, 2.0, true, 128))); + } + + public void testToXContent_WithoutParameters() throws IOException { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(null, null, null, null)); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + settings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is("{}")); + } + + public void testToXContent_WithParameters() throws IOException { + var settings = AzureAiStudioChatCompletionTaskSettings.fromMap(getTaskSettingsMap(1.0, 2.0, true, 512)); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + settings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"temperature":1.0,"top_p":2.0,"do_sample":true,"max_new_tokens":512}""")); + } + + public static Map getTaskSettingsMap( + @Nullable Double temperature, + @Nullable Double topP, + @Nullable Boolean doSample, + @Nullable Integer maxNewTokens + ) { + var map = new HashMap(); + + if (temperature != null) { + map.put(TEMPERATURE_FIELD, temperature); + } + + if (topP != null) { + map.put(TOP_P_FIELD, topP); + } + + if (doSample != null) { + map.put(DO_SAMPLE_FIELD, doSample); + } + + if (maxNewTokens != null) { + map.put(MAX_NEW_TOKENS_FIELD, maxNewTokens); + } + + return map; + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsModelTests.java new file mode 100644 index 0000000000000..5a450f03b4e01 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsModelTests.java @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.azureaistudio.embeddings; + +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.SimilarityMeasure; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; + +import java.net.URISyntaxException; + +import static org.elasticsearch.xpack.inference.services.azureaistudio.embeddings.AzureAiStudioEmbeddingsTaskSettingsTests.getTaskSettingsMap; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; + +public class AzureAiStudioEmbeddingsModelTests extends ESTestCase { + + public void testOverrideWith_OverridesUser() { + var model = createModel( + "id", + "http://testtarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + false, + null, + null, + null, + null + ); + + var requestTaskSettingsMap = getTaskSettingsMap("override_user"); + var overriddenModel = AzureAiStudioEmbeddingsModel.of(model, requestTaskSettingsMap); + + assertThat( + overriddenModel, + is( + createModel( + "id", + "http://testtarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + false, + null, + null, + "override_user", + null + ) + ) + ); + } + + public void testOverrideWith_OverridesWithoutValues() { + var model = createModel( + "id", + "http://testtarget.local", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + "apikey", + null, + false, + null, + null, + null, + null + ); + + var requestTaskSettingsMap = getTaskSettingsMap(null); + var overriddenModel = AzureAiStudioEmbeddingsModel.of(model, requestTaskSettingsMap); + + assertThat(overriddenModel, sameInstance(overriddenModel)); + } + + public void testSetsProperUrlForOpenAIModel() throws URISyntaxException { + var model = createModel("id", "http://testtarget.local", AzureAiStudioProvider.OPENAI, AzureAiStudioEndpointType.TOKEN, "apikey"); + assertThat(model.getEndpointUri().toString(), is("http://testtarget.local")); + } + + public void testSetsProperUrlForCohereModel() throws URISyntaxException { + var model = createModel("id", "http://testtarget.local", AzureAiStudioProvider.COHERE, AzureAiStudioEndpointType.TOKEN, "apikey"); + assertThat(model.getEndpointUri().toString(), is("http://testtarget.local/v1/embeddings")); + } + + public static AzureAiStudioEmbeddingsModel createModel( + String inferenceId, + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey + ) { + return createModel(inferenceId, target, provider, endpointType, apiKey, null, false, null, null, null, null); + } + + public static AzureAiStudioEmbeddingsModel createModel( + String inferenceId, + String target, + AzureAiStudioProvider provider, + AzureAiStudioEndpointType endpointType, + String apiKey, + @Nullable Integer dimensions, + boolean dimensionsSetByUser, + @Nullable Integer maxTokens, + @Nullable SimilarityMeasure similarity, + @Nullable String user, + RateLimitSettings rateLimitSettings + ) { + return new AzureAiStudioEmbeddingsModel( + inferenceId, + TaskType.TEXT_EMBEDDING, + "azureaistudio", + new AzureAiStudioEmbeddingsServiceSettings( + target, + provider, + endpointType, + dimensions, + dimensionsSetByUser, + maxTokens, + similarity, + rateLimitSettings + ), + new AzureAiStudioEmbeddingsTaskSettings(user), + new DefaultSecretSettings(new SecureString(apiKey.toCharArray())) + ); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsRequestTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsRequestTaskSettingsTests.java new file mode 100644 index 0000000000000..665d350bf249a --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsRequestTaskSettingsTests.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.azureaistudio.embeddings; + +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioEmbeddingsRequestTaskSettingsTests extends ESTestCase { + public void testFromMap_ReturnsEmptySettings_WhenTheMapIsEmpty() { + var settings = AzureAiStudioEmbeddingsRequestTaskSettings.fromMap(new HashMap<>(Map.of())); + assertThat(settings, is(AzureAiStudioEmbeddingsRequestTaskSettings.EMPTY_SETTINGS)); + } + + public void testFromMap_ReturnsEmptySettings_WhenTheMapDoesNotContainTheFields() { + var settings = AzureAiStudioEmbeddingsRequestTaskSettings.fromMap(new HashMap<>(Map.of("key", "model"))); + assertNull(settings.user()); + } + + public void testFromMap_ReturnsUser() { + var settings = AzureAiStudioEmbeddingsRequestTaskSettings.fromMap(new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, "user"))); + assertThat(settings.user(), is("user")); + } + + public void testFromMap_WhenUserIsEmpty_ThrowsValidationException() { + var exception = expectThrows( + ValidationException.class, + () -> AzureAiStudioEmbeddingsRequestTaskSettings.fromMap(new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, ""))) + ); + + assertThat(exception.getMessage(), containsString("[user] must be a non-empty string")); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsServiceSettingsTests.java new file mode 100644 index 0000000000000..283bfa1490df2 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsServiceSettingsTests.java @@ -0,0 +1,339 @@ +/* + * 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.azureaistudio.embeddings; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.SimilarityMeasure; +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.services.ConfigurationParseContext; +import org.elasticsearch.xpack.inference.services.ServiceFields; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioConstants; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioEndpointType; +import org.elasticsearch.xpack.inference.services.azureaistudio.AzureAiStudioProvider; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.elasticsearch.xpack.inference.services.ServiceFields.SIMILARITY; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioEmbeddingsServiceSettingsTests extends ESTestCase { + + public void testFromMap_Request_CreatesSettingsCorrectly() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + var dims = 1536; + var maxInputTokens = 512; + var serviceSettings = AzureAiStudioEmbeddingsServiceSettings.fromMap( + createRequestSettingsMap(target, provider, endpointType, dims, null, maxInputTokens, SimilarityMeasure.COSINE), + ConfigurationParseContext.REQUEST + ); + + assertThat( + serviceSettings, + is( + new AzureAiStudioEmbeddingsServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + dims, + true, + maxInputTokens, + SimilarityMeasure.COSINE, + null + ) + ) + ); + } + + public void testFromMap_RequestWithRateLimit_CreatesSettingsCorrectly() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + var dims = 1536; + var maxInputTokens = 512; + var settingsMap = createRequestSettingsMap(target, provider, endpointType, dims, null, maxInputTokens, SimilarityMeasure.COSINE); + settingsMap.put(RateLimitSettings.FIELD_NAME, new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, 3))); + + var serviceSettings = AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.REQUEST); + + assertThat( + serviceSettings, + is( + new AzureAiStudioEmbeddingsServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + dims, + true, + maxInputTokens, + SimilarityMeasure.COSINE, + new RateLimitSettings(3) + ) + ) + ); + } + + public void testFromMap_Request_DimensionsSetByUser_IsFalse_WhenDimensionsAreNotPresent() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + var maxInputTokens = 512; + var settingsMap = createRequestSettingsMap(target, provider, endpointType, null, null, maxInputTokens, SimilarityMeasure.COSINE); + var serviceSettings = AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.REQUEST); + + assertThat( + serviceSettings, + is( + new AzureAiStudioEmbeddingsServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + null, + false, + maxInputTokens, + SimilarityMeasure.COSINE, + null + ) + ) + ); + } + + public void testFromMap_Request_DimensionsSetByUser_ShouldThrowWhenPresent() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + var maxInputTokens = 512; + + var settingsMap = createRequestSettingsMap(target, provider, endpointType, null, true, maxInputTokens, SimilarityMeasure.COSINE); + + var thrownException = expectThrows( + ValidationException.class, + () -> AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.REQUEST) + ); + + MatcherAssert.assertThat( + thrownException.getMessage(), + containsString( + Strings.format( + "Validation Failed: 1: [service_settings] does not allow the setting [%s];", + AzureAiStudioConstants.DIMENSIONS_SET_BY_USER + ) + ) + ); + } + + public void testFromMap_Persistent_CreatesSettingsCorrectly() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + var dims = 1536; + var maxInputTokens = 512; + + var settingsMap = createRequestSettingsMap(target, provider, endpointType, dims, false, maxInputTokens, SimilarityMeasure.COSINE); + var serviceSettings = AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.PERSISTENT); + + assertThat( + serviceSettings, + is( + new AzureAiStudioEmbeddingsServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + dims, + false, + maxInputTokens, + SimilarityMeasure.COSINE, + null + ) + ) + ); + } + + public void testFromMap_PersistentContext_DoesNotThrowException_WhenDimensionsIsNull() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + + var settingsMap = createRequestSettingsMap(target, provider, endpointType, null, true, null, null); + var serviceSettings = AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.PERSISTENT); + + assertThat( + serviceSettings, + is( + new AzureAiStudioEmbeddingsServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + null, + true, + null, + null, + null + ) + ) + ); + } + + public void testFromMap_PersistentContext_DoesNotThrowException_WhenSimilarityIsPresent() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + + var settingsMap = createRequestSettingsMap(target, provider, endpointType, null, true, null, SimilarityMeasure.DOT_PRODUCT); + var serviceSettings = AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.PERSISTENT); + + assertThat( + serviceSettings, + is( + new AzureAiStudioEmbeddingsServiceSettings( + target, + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + null, + true, + null, + SimilarityMeasure.DOT_PRODUCT, + null + ) + ) + ); + } + + public void testFromMap_PersistentContext_ThrowsException_WhenDimensionsSetByUserIsNull() { + var target = "http://sometarget.local"; + var provider = "openai"; + var endpointType = "token"; + + var settingsMap = createRequestSettingsMap(target, provider, endpointType, 1, null, null, null); + + var exception = expectThrows( + ValidationException.class, + () -> AzureAiStudioEmbeddingsServiceSettings.fromMap(settingsMap, ConfigurationParseContext.PERSISTENT) + ); + + assertThat( + exception.getMessage(), + containsString("Validation Failed: 1: [service_settings] does not contain the required setting [dimensions_set_by_user];") + ); + } + + public void testToXContent_WritesDimensionsSetByUserTrue() throws IOException { + var entity = new AzureAiStudioEmbeddingsServiceSettings( + "target_value", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + null, + true, + null, + null, + new RateLimitSettings(2) + ); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, CoreMatchers.is(""" + {"target":"target_value","provider":"openai","endpoint_type":"token",""" + """ + "rate_limit":{"requests_per_minute":2},"dimensions_set_by_user":true}""")); + } + + public void testToXContent_WritesAllValues() throws IOException { + var entity = new AzureAiStudioEmbeddingsServiceSettings( + "target_value", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + 1024, + false, + 512, + null, + new RateLimitSettings(3) + ); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + entity.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, CoreMatchers.is(""" + {"target":"target_value","provider":"openai","endpoint_type":"token",""" + """ + "rate_limit":{"requests_per_minute":3},"dimensions":1024,"max_input_tokens":512,"dimensions_set_by_user":false}""")); + } + + public void testToFilteredXContent_WritesAllValues_ExceptDimensionsSetByUser() throws IOException { + var entity = new AzureAiStudioEmbeddingsServiceSettings( + "target_value", + AzureAiStudioProvider.OPENAI, + AzureAiStudioEndpointType.TOKEN, + 1024, + false, + 512, + null, + new RateLimitSettings(3) + ); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + var filteredXContent = entity.getFilteredXContentObject(); + filteredXContent.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, CoreMatchers.is(""" + {"target":"target_value","provider":"openai","endpoint_type":"token",""" + """ + "dimensions":1024,"max_input_tokens":512}""")); + } + + public static HashMap createRequestSettingsMap( + String target, + String provider, + String endpointType, + @Nullable Integer dimensions, + @Nullable Boolean dimensionsSetByUser, + @Nullable Integer maxTokens, + @Nullable SimilarityMeasure similarityMeasure + ) { + var map = new HashMap( + Map.of( + AzureAiStudioConstants.TARGET_FIELD, + target, + AzureAiStudioConstants.PROVIDER_FIELD, + provider, + AzureAiStudioConstants.ENDPOINT_TYPE_FIELD, + endpointType + ) + ); + + if (dimensions != null) { + map.put(ServiceFields.DIMENSIONS, dimensions); + } + + if (dimensionsSetByUser != null) { + map.put(AzureAiStudioConstants.DIMENSIONS_SET_BY_USER, dimensionsSetByUser.equals(Boolean.TRUE)); + } + + if (maxTokens != null) { + map.put(ServiceFields.MAX_INPUT_TOKENS, maxTokens); + } + + if (similarityMeasure != null) { + map.put(SIMILARITY, similarityMeasure.toString()); + } + + return map; + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsTaskSettingsTests.java new file mode 100644 index 0000000000000..3d1b7f0c7499c --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureaistudio/embeddings/AzureAiStudioEmbeddingsTaskSettingsTests.java @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.azureaistudio.embeddings; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.ValidationException; +import org.elasticsearch.core.Nullable; +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.services.azureaistudio.AzureAiStudioConstants; +import org.hamcrest.MatcherAssert; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.is; + +public class AzureAiStudioEmbeddingsTaskSettingsTests extends ESTestCase { + + public void testFromMap_WithUser() { + assertEquals( + new AzureAiStudioEmbeddingsTaskSettings("user"), + AzureAiStudioEmbeddingsTaskSettings.fromMap(new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, "user"))) + ); + } + + public void testFromMap_UserIsEmptyString() { + var thrownException = expectThrows( + ValidationException.class, + () -> AzureAiStudioEmbeddingsTaskSettings.fromMap(new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, ""))) + ); + + MatcherAssert.assertThat( + thrownException.getMessage(), + is(Strings.format("Validation Failed: 1: [task_settings] Invalid value empty string. [user] must be a non-empty string;")) + ); + } + + public void testFromMap_MissingUser_DoesNotThrowException() { + var taskSettings = AzureAiStudioEmbeddingsTaskSettings.fromMap(new HashMap<>(Map.of())); + assertNull(taskSettings.user()); + } + + public void testOverrideWith_KeepsOriginalValuesWithOverridesAreNull() { + var taskSettings = AzureAiStudioEmbeddingsTaskSettings.fromMap(new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, "user"))); + + var overriddenTaskSettings = AzureAiStudioEmbeddingsTaskSettings.of( + taskSettings, + AzureAiStudioEmbeddingsRequestTaskSettings.EMPTY_SETTINGS + ); + MatcherAssert.assertThat(overriddenTaskSettings, is(taskSettings)); + } + + public void testOverrideWith_UsesOverriddenSettings() { + var taskSettings = AzureAiStudioEmbeddingsTaskSettings.fromMap(new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, "user"))); + + var requestTaskSettings = AzureAiStudioEmbeddingsRequestTaskSettings.fromMap( + new HashMap<>(Map.of(AzureAiStudioConstants.USER_FIELD, "user2")) + ); + + var overriddenTaskSettings = AzureAiStudioEmbeddingsTaskSettings.of(taskSettings, requestTaskSettings); + MatcherAssert.assertThat(overriddenTaskSettings, is(new AzureAiStudioEmbeddingsTaskSettings("user2"))); + } + + public void testToXContent_WithoutParameters() throws IOException { + var settings = AzureAiStudioEmbeddingsTaskSettings.fromMap(getTaskSettingsMap(null)); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + settings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is("{}")); + } + + public void testToXContent_WithParameters() throws IOException { + var settings = AzureAiStudioEmbeddingsTaskSettings.fromMap(getTaskSettingsMap("testuser")); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + settings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"user":"testuser"}""")); + } + + public static Map getTaskSettingsMap(@Nullable String user) { + Map map = new HashMap<>(); + if (user != null) { + map.put(AzureAiStudioConstants.USER_FIELD, user); + } + return map; + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiSecretSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiSecretSettingsTests.java index d2b83d7b14e2b..bfff26bcaefc0 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiSecretSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/AzureOpenAiSecretSettingsTests.java @@ -143,7 +143,7 @@ protected AzureOpenAiSecretSettings createTestInstance() { @Override protected AzureOpenAiSecretSettings mutateInstance(AzureOpenAiSecretSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, AzureOpenAiSecretSettingsTests::createRandom); } public static Map getAzureOpenAiSecretSettingsMap(@Nullable String apiKey, @Nullable String entraId) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionServiceSettingsTests.java index cbaa41c37958d..46e514c8b16c4 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionServiceSettingsTests.java @@ -87,6 +87,6 @@ protected AzureOpenAiCompletionServiceSettings createTestInstance() { @Override protected AzureOpenAiCompletionServiceSettings mutateInstance(AzureOpenAiCompletionServiceSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, AzureOpenAiCompletionServiceSettingsTests::createRandom); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionTaskSettingsTests.java index 7f0e730b8835c..15e1d8d7809c5 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionTaskSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/completion/AzureOpenAiCompletionTaskSettingsTests.java @@ -94,6 +94,6 @@ protected AzureOpenAiCompletionTaskSettings createTestInstance() { @Override protected AzureOpenAiCompletionTaskSettings mutateInstance(AzureOpenAiCompletionTaskSettings instance) throws IOException { - return createRandomWithUser(); + return randomValueOtherThan(instance, AzureOpenAiCompletionTaskSettingsTests::createRandomWithUser); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsServiceSettingsTests.java index 7c56ffad27c80..f4c6f9b2a4f07 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsServiceSettingsTests.java @@ -423,7 +423,7 @@ protected AzureOpenAiEmbeddingsServiceSettings createTestInstance() { @Override protected AzureOpenAiEmbeddingsServiceSettings mutateInstance(AzureOpenAiEmbeddingsServiceSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, AzureOpenAiEmbeddingsServiceSettingsTests::createRandom); } public static Map getPersistentAzureOpenAiServiceSettingsMap( diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsTaskSettingsTests.java index cc2d8b9b67620..324bdd15d9256 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsTaskSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/azureopenai/embeddings/AzureOpenAiEmbeddingsTaskSettingsTests.java @@ -92,7 +92,7 @@ protected AzureOpenAiEmbeddingsTaskSettings createTestInstance() { @Override protected AzureOpenAiEmbeddingsTaskSettings mutateInstance(AzureOpenAiEmbeddingsTaskSettings instance) throws IOException { - return createRandomWithUser(); + return randomValueOtherThan(instance, AzureOpenAiEmbeddingsTaskSettingsTests::createRandomWithUser); } public static Map getAzureOpenAiRequestTaskSettingsMap(@Nullable String user) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceSettingsTests.java index a010f63802052..303ed1cab2c50 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/CohereServiceSettingsTests.java @@ -275,7 +275,7 @@ protected CohereServiceSettings createTestInstance() { @Override protected CohereServiceSettings mutateInstance(CohereServiceSettings instance) throws IOException { - return null; + return randomValueOtherThan(instance, CohereServiceSettingsTests::createRandom); } public static Map getServiceSettingsMap(@Nullable String url, @Nullable String model) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionModelTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionModelTests.java new file mode 100644 index 0000000000000..5352d8006f1e1 --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionModelTests.java @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.cohere.completion; + +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.inference.EmptyTaskSettings; +import org.elasticsearch.inference.TaskType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.inference.services.settings.DefaultSecretSettings; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.is; + +public class CohereCompletionModelTests extends ESTestCase { + + public void testCreateModel_AlwaysWithEmptyTaskSettings() { + var model = new CohereCompletionModel( + "model", + TaskType.COMPLETION, + "service", + new HashMap<>(Map.of()), + new HashMap<>(Map.of("model", "overridden model")), + null + ); + + assertThat(model.getTaskSettings(), is(EmptyTaskSettings.INSTANCE)); + } + + public static CohereCompletionModel createModel(String url, String apiKey, @Nullable String model) { + return new CohereCompletionModel( + "id", + TaskType.COMPLETION, + "service", + new CohereCompletionServiceSettings(url, model, null), + new EmptyTaskSettings(), + new DefaultSecretSettings(new SecureString(apiKey.toCharArray())) + ); + } + +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionServiceSettingsTests.java new file mode 100644 index 0000000000000..f4cab3c2b0f1e --- /dev/null +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/completion/CohereCompletionServiceSettingsTests.java @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.inference.services.cohere.completion; + +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.ServiceFields; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettings; +import org.elasticsearch.xpack.inference.services.settings.RateLimitSettingsTests; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.is; + +public class CohereCompletionServiceSettingsTests extends AbstractWireSerializingTestCase { + + public static CohereCompletionServiceSettings createRandom() { + return new CohereCompletionServiceSettings(randomAlphaOfLength(8), randomAlphaOfLength(8), RateLimitSettingsTests.createRandom()); + } + + public void testFromMap_WithRateLimitSettingsNull() { + var url = "https://www.abc.com"; + var model = "model"; + + var serviceSettings = CohereCompletionServiceSettings.fromMap( + new HashMap<>(Map.of(ServiceFields.URL, url, ServiceFields.MODEL_ID, model)) + ); + + assertThat(serviceSettings, is(new CohereCompletionServiceSettings(url, model, null))); + } + + public void testFromMap_WithRateLimitSettings() { + var url = "https://www.abc.com"; + var model = "model"; + var requestsPerMinute = 100; + + var serviceSettings = CohereCompletionServiceSettings.fromMap( + new HashMap<>( + Map.of( + ServiceFields.URL, + url, + ServiceFields.MODEL_ID, + model, + RateLimitSettings.FIELD_NAME, + new HashMap<>(Map.of(RateLimitSettings.REQUESTS_PER_MINUTE_FIELD, requestsPerMinute)) + ) + ) + ); + + assertThat(serviceSettings, is(new CohereCompletionServiceSettings(url, model, new RateLimitSettings(requestsPerMinute)))); + } + + public void testToXContent_WritesAllValues() throws IOException { + var serviceSettings = new CohereCompletionServiceSettings("url", "model", new RateLimitSettings(3)); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + serviceSettings.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"url":"url","model_id":"model","rate_limit":{"requests_per_minute":3}}""")); + } + + public void testToXContent_WithFilteredObject_WritesAllValues_Except_RateLimit() throws IOException { + var serviceSettings = new CohereCompletionServiceSettings("url", "model", new RateLimitSettings(3)); + + XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON); + var filteredXContent = serviceSettings.getFilteredXContentObject(); + filteredXContent.toXContent(builder, null); + String xContentResult = Strings.toString(builder); + + assertThat(xContentResult, is(""" + {"url":"url","model_id":"model"}""")); + } + + @Override + protected Writeable.Reader instanceReader() { + return CohereCompletionServiceSettings::new; + } + + @Override + protected CohereCompletionServiceSettings createTestInstance() { + return createRandom(); + } + + @Override + protected CohereCompletionServiceSettings mutateInstance(CohereCompletionServiceSettings instance) throws IOException { + return randomValueOtherThan(instance, this::createTestInstance); + } +} diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsServiceSettingsTests.java index a306a3e660cd9..6f8fe6344b57f 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsServiceSettingsTests.java @@ -358,7 +358,7 @@ protected CohereEmbeddingsServiceSettings createTestInstance() { @Override protected CohereEmbeddingsServiceSettings mutateInstance(CohereEmbeddingsServiceSettings instance) throws IOException { - return null; + return randomValueOtherThan(instance, CohereEmbeddingsServiceSettingsTests::createRandom); } @Override diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsTaskSettingsTests.java index 4f5d872f09eb8..c18310eb9a84a 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsTaskSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/embeddings/CohereEmbeddingsTaskSettingsTests.java @@ -156,7 +156,7 @@ protected CohereEmbeddingsTaskSettings createTestInstance() { @Override protected CohereEmbeddingsTaskSettings mutateInstance(CohereEmbeddingsTaskSettings instance) throws IOException { - return null; + return randomValueOtherThan(instance, CohereEmbeddingsTaskSettingsTests::createRandom); } public static Map getTaskSettingsMapEmpty() { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/rerank/CohereRerankServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/rerank/CohereRerankServiceSettingsTests.java index cb30077fec174..4943ddf74fda1 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/rerank/CohereRerankServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/cohere/rerank/CohereRerankServiceSettingsTests.java @@ -77,7 +77,7 @@ protected CohereRerankServiceSettings createTestInstance() { @Override protected CohereRerankServiceSettings mutateInstance(CohereRerankServiceSettings instance) throws IOException { - return null; + return randomValueOtherThan(instance, CohereRerankServiceSettingsTests::createRandom); } @Override diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/HuggingFaceServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/HuggingFaceServiceSettingsTests.java index d81c94a0dedda..9d92f756dd31c 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/HuggingFaceServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/HuggingFaceServiceSettingsTests.java @@ -197,7 +197,7 @@ protected HuggingFaceServiceSettings createTestInstance() { @Override protected HuggingFaceServiceSettings mutateInstance(HuggingFaceServiceSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, HuggingFaceServiceSettingsTests::createRandom); } public static Map getServiceSettingsMap(String url) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserSecretSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserSecretSettingsTests.java index 2b8281da8db13..f69a9b5a967e0 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserSecretSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserSecretSettingsTests.java @@ -77,6 +77,6 @@ protected HuggingFaceElserSecretSettings createTestInstance() { @Override protected HuggingFaceElserSecretSettings mutateInstance(HuggingFaceElserSecretSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, HuggingFaceElserSecretSettingsTests::createRandom); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserServiceSettingsTests.java index eadefddecce70..bd6a5007b72ee 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/huggingface/elser/HuggingFaceElserServiceSettingsTests.java @@ -122,6 +122,6 @@ protected HuggingFaceElserServiceSettings createTestInstance() { @Override protected HuggingFaceElserServiceSettings mutateInstance(HuggingFaceElserServiceSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, HuggingFaceElserServiceSettingsTests::createRandom); } } diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/completion/OpenAiChatCompletionServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/completion/OpenAiChatCompletionServiceSettingsTests.java index b9b4310699d07..75ea63eba8a34 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/completion/OpenAiChatCompletionServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/completion/OpenAiChatCompletionServiceSettingsTests.java @@ -236,7 +236,7 @@ protected OpenAiChatCompletionServiceSettings createTestInstance() { @Override protected OpenAiChatCompletionServiceSettings mutateInstance(OpenAiChatCompletionServiceSettings instance) throws IOException { - return createRandomWithNonNullUrl(); + return randomValueOtherThan(instance, OpenAiChatCompletionServiceSettingsTests::createRandomWithNonNullUrl); } private static OpenAiChatCompletionServiceSettings createRandomWithNonNullUrl() { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsServiceSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsServiceSettingsTests.java index c964d2643459d..1be70ee586835 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsServiceSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsServiceSettingsTests.java @@ -441,7 +441,7 @@ protected OpenAiEmbeddingsServiceSettings createTestInstance() { @Override protected OpenAiEmbeddingsServiceSettings mutateInstance(OpenAiEmbeddingsServiceSettings instance) throws IOException { - return createRandomWithNonNullUrl(); + return randomValueOtherThan(instance, OpenAiEmbeddingsServiceSettingsTests::createRandomWithNonNullUrl); } public static Map getServiceSettingsMap(String modelId, @Nullable String url, @Nullable String org) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsTaskSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsTaskSettingsTests.java index c5a510ef9de0c..464f5a1885d99 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsTaskSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/openai/embeddings/OpenAiEmbeddingsTaskSettingsTests.java @@ -97,7 +97,7 @@ protected OpenAiEmbeddingsTaskSettings createTestInstance() { @Override protected OpenAiEmbeddingsTaskSettings mutateInstance(OpenAiEmbeddingsTaskSettings instance) throws IOException { - return createRandomWithUser(); + return randomValueOtherThan(instance, OpenAiEmbeddingsTaskSettingsTests::createRandomWithUser); } public static Map getTaskSettingsMap(@Nullable String user) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/DefaultSecretSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/DefaultSecretSettingsTests.java index bd7a3ef4dcf03..212a867349e5c 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/DefaultSecretSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/DefaultSecretSettingsTests.java @@ -75,7 +75,7 @@ protected DefaultSecretSettings createTestInstance() { @Override protected DefaultSecretSettings mutateInstance(DefaultSecretSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, DefaultSecretSettingsTests::createRandom); } public static Map getSecretSettingsMap(String apiKey) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettingsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettingsTests.java index 65bcaca981020..cdee7c452ff52 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettingsTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/services/settings/RateLimitSettingsTests.java @@ -100,6 +100,6 @@ protected RateLimitSettings createTestInstance() { @Override protected RateLimitSettings mutateInstance(RateLimitSettings instance) throws IOException { - return createRandom(); + return randomValueOtherThan(instance, RateLimitSettingsTests::createRandom); } } diff --git a/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetFlameGraphActionIT.java b/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetFlameGraphActionIT.java index 49a5cfa7ca067..db343b62c5a1d 100644 --- a/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetFlameGraphActionIT.java +++ b/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetFlameGraphActionIT.java @@ -22,6 +22,7 @@ public void testGetStackTracesUnfiltered() throws Exception { null, null, null, + null, null ); GetFlamegraphResponse response = client().execute(GetFlamegraphAction.INSTANCE, request).get(); diff --git a/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetStackTracesActionIT.java b/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetStackTracesActionIT.java index 9de148c33c467..6463cda554e5b 100644 --- a/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetStackTracesActionIT.java +++ b/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetStackTracesActionIT.java @@ -28,6 +28,7 @@ public void testGetStackTracesUnfiltered() throws Exception { null, null, null, + null, null ); request.setAdjustSampleCount(true); @@ -72,6 +73,7 @@ public void testGetStackTracesGroupedByServiceName() throws Exception { null, null, null, + null, null ); request.setAdjustSampleCount(true); @@ -91,7 +93,7 @@ public void testGetStackTracesGroupedByServiceName() throws Exception { assertEquals(18, stackTrace.typeIds.length); assertEquals(0.0000048475146d, stackTrace.annualCO2Tons, 0.0000000001d); assertEquals(0.18834d, stackTrace.annualCostsUSD, 0.00001d); - assertEquals(Long.valueOf(2L), stackTrace.subGroups.get("basket")); + assertEquals(Long.valueOf(2L), stackTrace.subGroups.getCount("basket")); assertNotNull(response.getStackFrames()); StackFrame stackFrame = response.getStackFrames().get("8NlMClggx8jaziUTJXlmWAAAAAAAAIYI"); @@ -101,28 +103,6 @@ public void testGetStackTracesGroupedByServiceName() throws Exception { assertEquals("vmlinux", response.getExecutables().get("lHp5_WAgpLy2alrUVab6HA")); } - public void testGetStackTracesGroupedByInvalidField() { - GetStackTracesRequest request = new GetStackTracesRequest( - 1000, - 600.0d, - 1.0d, - 1.0d, - null, - null, - null, - // only service.name is supported (note the trailing "s") - "service.names", - null, - null, - null, - null, - null - ); - request.setAdjustSampleCount(true); - IllegalArgumentException e = expectThrows(IllegalArgumentException.class, client().execute(GetStackTracesAction.INSTANCE, request)); - assertEquals("Requested custom event aggregation field [service.names] but only [service.name] is supported.", e.getMessage()); - } - public void testGetStackTracesFromAPMWithMatchNoDownsampling() throws Exception { BoolQueryBuilder query = QueryBuilders.boolQuery(); query.must().add(QueryBuilders.termQuery("transaction.name", "encodeSha1")); @@ -142,6 +122,7 @@ public void testGetStackTracesFromAPMWithMatchNoDownsampling() throws Exception null, null, null, + null, null ); GetStackTracesResponse response = client().execute(GetStackTracesAction.INSTANCE, request).get(); @@ -161,7 +142,7 @@ public void testGetStackTracesFromAPMWithMatchNoDownsampling() throws Exception assertEquals(39, stackTrace.typeIds.length); assertTrue(stackTrace.annualCO2Tons > 0.0d); assertTrue(stackTrace.annualCostsUSD > 0.0d); - assertEquals(Long.valueOf(3L), stackTrace.subGroups.get("encodeSha1")); + assertEquals(Long.valueOf(3L), stackTrace.subGroups.getCount("encodeSha1")); assertNotNull(response.getStackFrames()); StackFrame stackFrame = response.getStackFrames().get("fhsEKXDuxJ-jIJrZpdRuSAAAAAAAAFtj"); @@ -187,6 +168,7 @@ public void testGetStackTracesFromAPMWithMatchAndDownsampling() throws Exception null, null, null, + null, null ); // ensures consistent results in the random sampler aggregation that is used internally @@ -237,6 +219,7 @@ public void testGetStackTracesFromAPMNoMatch() throws Exception { null, null, null, + null, null ); GetStackTracesResponse response = client().execute(GetStackTracesAction.INSTANCE, request).get(); @@ -259,6 +242,7 @@ public void testGetStackTracesFromAPMIndexNotAvailable() throws Exception { null, null, null, + null, null ); GetStackTracesResponse response = client().execute(GetStackTracesAction.INSTANCE, request).get(); @@ -281,6 +265,7 @@ public void testGetStackTracesFromAPMStackTraceFieldNotAvailable() throws Except null, null, null, + null, null ); GetStackTracesResponse response = client().execute(GetStackTracesAction.INSTANCE, request).get(); diff --git a/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsActionIT.java b/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsActionIT.java index ab5bbc3812eb5..c6250dae4d649 100644 --- a/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsActionIT.java +++ b/x-pack/plugin/profiling/src/internalClusterTest/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsActionIT.java @@ -25,6 +25,7 @@ public void testGetTopNFunctionsUnfiltered() throws Exception { null, null, null, + null, null ); request.setAdjustSampleCount(true); @@ -46,6 +47,7 @@ public void testGetTopNFunctionsGroupedByServiceName() throws Exception { null, null, null, + null, null ); request.setAdjustSampleCount(true); @@ -73,6 +75,7 @@ public void testGetTopNFunctionsFromAPM() throws Exception { null, null, null, + null, null ); GetTopNFunctionsResponse response = client().execute(GetTopNFunctionsAction.INSTANCE, request).get(); diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequest.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequest.java index be30c9662fddb..6bd93c6df6cc8 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequest.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequest.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.support.TransportAction; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.UpdateForV9; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.tasks.CancellableTask; import org.elasticsearch.tasks.Task; @@ -42,7 +43,9 @@ public class GetStackTracesRequest extends ActionRequest implements IndicesReque public static final ParseField LIMIT_FIELD = new ParseField("limit"); public static final ParseField INDICES_FIELD = new ParseField("indices"); public static final ParseField STACKTRACE_IDS_FIELD = new ParseField("stacktrace_ids_field"); + @UpdateForV9 // Remove this BWC layer and allow only AGGREGATION_FIELDS public static final ParseField AGGREGATION_FIELD = new ParseField("aggregation_field"); + public static final ParseField AGGREGATION_FIELDS = new ParseField("aggregation_fields"); public static final ParseField REQUESTED_DURATION_FIELD = new ParseField("requested_duration"); public static final ParseField AWS_COST_FACTOR_FIELD = new ParseField("aws_cost_factor"); public static final ParseField AZURE_COST_FACTOR_FIELD = new ParseField("azure_cost_factor"); @@ -59,7 +62,9 @@ public class GetStackTracesRequest extends ActionRequest implements IndicesReque private String[] indices; private boolean userProvidedIndices; private String stackTraceIdsField; + @UpdateForV9 // Remove this BWC layer and allow only aggregationFields private String aggregationField; + private String[] aggregationFields; private Double requestedDuration; private Double awsCostFactor; private Double azureCostFactor; @@ -78,7 +83,7 @@ public class GetStackTracesRequest extends ActionRequest implements IndicesReque private Integer shardSeed; public GetStackTracesRequest() { - this(null, null, null, null, null, null, null, null, null, null, null, null, null); + this(null, null, null, null, null, null, null, null, null, null, null, null, null, null); } public GetStackTracesRequest( @@ -90,6 +95,7 @@ public GetStackTracesRequest( String[] indices, String stackTraceIdsField, String aggregationField, + String[] aggregationFields, Double customCO2PerKWH, Double customDatacenterPUE, Double customPerCoreWattX86, @@ -105,6 +111,7 @@ public GetStackTracesRequest( this.userProvidedIndices = indices != null && indices.length > 0; this.stackTraceIdsField = stackTraceIdsField; this.aggregationField = aggregationField; + this.aggregationFields = aggregationFields; this.customCO2PerKWH = customCO2PerKWH; this.customDatacenterPUE = customDatacenterPUE; this.customPerCoreWattX86 = customPerCoreWattX86; @@ -181,6 +188,19 @@ public String getAggregationField() { return aggregationField; } + public String[] getAggregationFields() { + return aggregationField != null ? new String[] { aggregationField } : aggregationFields; + } + + public boolean hasAggregationFields() { + String[] f = getAggregationFields(); + return f != null && f.length > 0; + } + + public boolean isLegacyAggregationField() { + return aggregationField != null; + } + public boolean isAdjustSampleCount() { return Boolean.TRUE.equals(adjustSampleCount); } @@ -244,8 +264,10 @@ public void parseXContent(XContentParser parser) throws IOException { } } else if (token == XContentParser.Token.START_ARRAY) { if (INDICES_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - this.indices = parseIndices(parser); + this.indices = parseToStringArray(parser, INDICES_FIELD); this.userProvidedIndices = true; + } else if (AGGREGATION_FIELDS.match(currentFieldName, parser.getDeprecationHandler())) { + this.aggregationFields = parseToStringArray(parser, AGGREGATION_FIELDS); } else { throw new ParsingException(parser.getTokenLocation(), "Unexpected token " + token + " in [" + currentFieldName + "]."); } @@ -260,12 +282,12 @@ public void parseXContent(XContentParser parser) throws IOException { } } - private String[] parseIndices(XContentParser parser) throws IOException { + private String[] parseToStringArray(XContentParser parser, ParseField parseField) throws IOException { XContentParser.Token token; - List indices = new ArrayList<>(); + List values = new ArrayList<>(); while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { if (token == XContentParser.Token.VALUE_STRING) { - indices.add(parser.text()); + values.add(parser.text()); } else { throw new ParsingException( parser.getTokenLocation(), @@ -274,12 +296,12 @@ private String[] parseIndices(XContentParser parser) throws IOException { + "] but found [" + token + "] in [" - + INDICES_FIELD.getPreferredName() + + parseField.getPreferredName() + "]." ); } } - return indices.toArray(new String[0]); + return values.toArray(new String[0]); } @Override @@ -300,6 +322,32 @@ public ActionRequestValidationException validate() { ); } } + if (aggregationField != null && aggregationFields != null) { + validationException = addValidationError( + "[" + + AGGREGATION_FIELD.getPreferredName() + + "] must not be set when [" + + AGGREGATION_FIELDS.getPreferredName() + + "] is also set", + validationException + ); + + } + if (aggregationFields != null) { + // limit so we avoid an explosion of buckets + if (aggregationFields.length < 1 || aggregationFields.length > 2) { + validationException = addValidationError( + "[" + + AGGREGATION_FIELDS.getPreferredName() + + "] must contain either one or two elements but contains [" + + aggregationFields.length + + "] elements.", + validationException + ); + } + + } + if (aggregationField != null && aggregationField.isBlank()) { validationException = addValidationError( "[" + AGGREGATION_FIELD.getPreferredName() + "] must be non-empty", @@ -339,6 +387,7 @@ public String getDescription() { appendField(sb, "indices", indices); appendField(sb, "stacktrace_ids_field", stackTraceIdsField); appendField(sb, "aggregation_field", aggregationField); + appendField(sb, "aggregation_fields", aggregationFields); appendField(sb, "sample_size", sampleSize); appendField(sb, "limit", limit); appendField(sb, "requested_duration", requestedDuration); diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesResponseBuilder.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesResponseBuilder.java index 1b31642d07be1..8bb207c0f990f 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesResponseBuilder.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetStackTracesResponseBuilder.java @@ -155,7 +155,7 @@ public GetStackTracesResponse build() { if (event != null) { StackTrace stackTrace = entry.getValue(); stackTrace.count = event.count; - if (event.subGroups.isEmpty() == false) { + if (event.subGroups != null) { stackTrace.subGroups = event.subGroups; } stackTrace.annualCO2Tons = event.annualCO2Tons; diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/StackTrace.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/StackTrace.java index 2a4e5f42fe657..0be6d91450eda 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/StackTrace.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/StackTrace.java @@ -26,7 +26,7 @@ final class StackTrace implements ToXContentObject { String[] fileIds; String[] frameIds; int[] typeIds; - Map subGroups; + SubGroup subGroups; double annualCO2Tons; double annualCostsUSD; long count; diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/SubGroup.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/SubGroup.java new file mode 100644 index 0000000000000..25ba70ee7185a --- /dev/null +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/SubGroup.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.profiling.action; + +import org.elasticsearch.core.UpdateForV9; +import org.elasticsearch.xcontent.ToXContentFragment; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class SubGroup implements ToXContentFragment { + private final String name; + private Long count; + @UpdateForV9 // remove legacy XContent rendering + private final boolean renderLegacyXContent; + private final Map subgroups; + + public static SubGroup root(String name, boolean renderLegacyXContent) { + return new SubGroup(name, null, renderLegacyXContent, new HashMap<>()); + } + + public SubGroup(String name, Long count, boolean renderLegacyXContent, Map subgroups) { + this.name = name; + this.count = count; + this.renderLegacyXContent = renderLegacyXContent; + this.subgroups = subgroups; + } + + public SubGroup addCount(String name, long count) { + if (this.subgroups.containsKey(name) == false) { + this.subgroups.put(name, new SubGroup(name, count, renderLegacyXContent, new HashMap<>())); + } else { + SubGroup s = this.subgroups.get(name); + s.count += count; + } + return this; + } + + public SubGroup getOrAddChild(String name) { + if (subgroups.containsKey(name) == false) { + this.subgroups.put(name, new SubGroup(name, null, renderLegacyXContent, new HashMap<>())); + } + return this.subgroups.get(name); + } + + public Long getCount(String name) { + SubGroup subGroup = this.subgroups.get(name); + return subGroup != null ? subGroup.count : null; + } + + public SubGroup getSubGroup(String name) { + return this.subgroups.get(name); + } + + public SubGroup copy() { + Map copy = new HashMap<>(subgroups.size()); + for (Map.Entry subGroup : subgroups.entrySet()) { + copy.put(subGroup.getKey(), subGroup.getValue().copy()); + } + return new SubGroup(name, count, renderLegacyXContent, copy); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (renderLegacyXContent) { + // This assumes that we only have one level of sub groups + if (subgroups != null && subgroups.isEmpty() == false) { + for (SubGroup subgroup : subgroups.values()) { + builder.field(subgroup.name, subgroup.count); + } + } + return builder; + } else { + builder.startObject(name); + // only the root node has no count + if (count != null) { + builder.field("count", count); + } + if (subgroups != null && subgroups.isEmpty() == false) { + for (SubGroup subgroup : subgroups.values()) { + subgroup.toXContent(builder, params); + } + } + return builder.endObject(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SubGroup subGroup = (SubGroup) o; + return Objects.equals(name, subGroup.name) + && Objects.equals(count, subGroup.count) + && Objects.equals(subgroups, subGroup.subgroups); + } + + @Override + public int hashCode() { + return Objects.hash(name, count, subgroups); + } + + @Override + public String toString() { + return name; + } + + public void merge(SubGroup s) { + if (s == null) { + return; + } + // must have the same name + if (this.name.equals(s.name)) { + if (this.count != null && s.count != null) { + this.count += s.count; + } else if (this.count == null) { + this.count = s.count; + } + for (SubGroup subGroup : s.subgroups.values()) { + if (this.subgroups.containsKey(subGroup.name)) { + // merge + this.subgroups.get(subGroup.name).merge(subGroup); + } else { + // add sub group as is (recursively) + this.subgroups.put(subGroup.name, subGroup.copy()); + } + } + } + } +} diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/SubGroupCollector.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/SubGroupCollector.java new file mode 100644 index 0000000000000..63491a63243dc --- /dev/null +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/SubGroupCollector.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.profiling.action; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; +import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; + +import java.util.Iterator; + +public final class SubGroupCollector { + /** + * Users may provide a custom field via the API that is used to sub-divide profiling events. This is useful in the context of TopN + * where we want to provide additional breakdown of where a certain function has been called (e.g. a certain service or transaction). + */ + static final String CUSTOM_EVENT_SUB_AGGREGATION_NAME = "custom_event_group_"; + + private static final Logger log = LogManager.getLogger(SubGroupCollector.class); + + private final String[] aggregationFields; + private final boolean legacyAggregationField; + + public static SubGroupCollector attach( + AbstractAggregationBuilder parentAggregation, + String[] aggregationFields, + boolean legacyAggregationField + ) { + SubGroupCollector c = new SubGroupCollector(aggregationFields, legacyAggregationField); + c.addAggregations(parentAggregation); + return c; + } + + private SubGroupCollector(String[] aggregationFields, boolean legacyAggregationField) { + this.aggregationFields = aggregationFields; + this.legacyAggregationField = legacyAggregationField; + } + + private boolean hasAggregationFields() { + return aggregationFields != null && aggregationFields.length > 0; + } + + private void addAggregations(AbstractAggregationBuilder parentAggregation) { + if (hasAggregationFields()) { + // cast to Object to disambiguate this from a varargs call + log.trace("Grouping stacktrace events by {}.", (Object) aggregationFields); + AbstractAggregationBuilder parentAgg = parentAggregation; + for (String aggregationField : aggregationFields) { + String aggName = CUSTOM_EVENT_SUB_AGGREGATION_NAME + aggregationField; + TermsAggregationBuilder agg = new TermsAggregationBuilder(aggName).field(aggregationField); + parentAgg.subAggregation(agg); + parentAgg = agg; + } + } + } + + void collectResults(MultiBucketsAggregation.Bucket bucket, TraceEvent event) { + collectResults(new BucketAdapter(bucket), event); + } + + void collectResults(Bucket bucket, TraceEvent event) { + if (hasAggregationFields()) { + if (event.subGroups == null) { + event.subGroups = SubGroup.root(aggregationFields[0], legacyAggregationField); + } + collectInternal(bucket.getAggregations(), event.subGroups, 0); + } + } + + private void collectInternal(Agg parentAgg, SubGroup parentGroup, int aggField) { + if (aggField == aggregationFields.length) { + return; + } + String aggName = CUSTOM_EVENT_SUB_AGGREGATION_NAME + aggregationFields[aggField]; + for (Bucket b : parentAgg.getBuckets(aggName)) { + String subGroupName = b.getKey(); + parentGroup.addCount(subGroupName, b.getCount()); + SubGroup currentGroup = parentGroup.getSubGroup(subGroupName); + int nextAggField = aggField + 1; + if (nextAggField < aggregationFields.length) { + collectInternal(b.getAggregations(), currentGroup.getOrAddChild(aggregationFields[nextAggField]), nextAggField); + } + } + } + + // The sole purpose of the code below is to abstract our code from the aggs framework to make it unit-testable + interface Agg { + Iterable getBuckets(String aggName); + + } + + interface Bucket { + String getKey(); + + long getCount(); + + Agg getAggregations(); + } + + static class InternalAggregationAdapter implements Agg { + private final InternalAggregations agg; + + InternalAggregationAdapter(InternalAggregations agg) { + this.agg = agg; + } + + @Override + public Iterable getBuckets(String aggName) { + MultiBucketsAggregation multiBucketsAggregation = agg.get(aggName); + return () -> { + Iterator it = multiBucketsAggregation.getBuckets().iterator(); + return new Iterator<>() { + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public Bucket next() { + return new BucketAdapter(it.next()); + } + }; + }; + } + } + + static class BucketAdapter implements Bucket { + private final MultiBucketsAggregation.Bucket bucket; + + BucketAdapter(MultiBucketsAggregation.Bucket bucket) { + this.bucket = bucket; + } + + @Override + public String getKey() { + return bucket.getKeyAsString(); + } + + @Override + public long getCount() { + return bucket.getDocCount(); + } + + @Override + public Agg getAggregations() { + return new InternalAggregationAdapter(bucket.getAggregations()); + } + } +} diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TopNFunction.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TopNFunction.java index 800b006b3cc17..87b32698db8d1 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TopNFunction.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TopNFunction.java @@ -11,11 +11,9 @@ import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; -final class TopNFunction implements Cloneable, ToXContentObject, Comparable { +final class TopNFunction implements ToXContentObject, Comparable { private final String id; private int rank; private final int frameType; @@ -31,7 +29,7 @@ final class TopNFunction implements Cloneable, ToXContentObject, Comparable subGroups; + private SubGroup subGroups; TopNFunction( String id, @@ -59,7 +57,7 @@ final class TopNFunction implements Cloneable, ToXContentObject, Comparable() + null ); } @@ -79,7 +77,7 @@ final class TopNFunction implements Cloneable, ToXContentObject, Comparable subGroups + SubGroup subGroups ) { this.id = id; this.rank = rank; @@ -147,15 +145,15 @@ public void addTotalAnnualCostsUSD(double costs) { this.totalAnnualCostsUSD += costs; } - public void addSubGroups(Map subGroups) { - for (Map.Entry subGroup : subGroups.entrySet()) { - long count = this.subGroups.getOrDefault(subGroup.getKey(), 0L); - this.subGroups.put(subGroup.getKey(), count + subGroup.getValue()); + public void addSubGroups(SubGroup subGroups) { + if (this.subGroups == null) { + this.subGroups = subGroups.copy(); + } else { + this.subGroups.merge(subGroups); } } - @Override - protected TopNFunction clone() { + public TopNFunction copy() { return new TopNFunction( id, rank, @@ -172,7 +170,7 @@ protected TopNFunction clone() { totalAnnualCO2Tons, selfAnnualCostsUSD, totalAnnualCostsUSD, - new HashMap<>(subGroups) + subGroups.copy() ); } @@ -190,7 +188,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field("line_number", this.sourceLine); builder.field("executable_file_name", this.exeFilename); builder.endObject(); - builder.field("sub_groups", subGroups); + if (subGroups != null) { + builder.startObject("sub_groups"); + subGroups.toXContent(builder, params); + builder.endObject(); + } builder.field("self_count", this.selfCount); builder.field("total_count", this.totalCount); builder.field("self_annual_co2_tons").rawValue(NumberUtils.doubleToString(selfAnnualCO2Tons)); diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TraceEvent.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TraceEvent.java index f020ad9e6a905..b2c50512a5b9c 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TraceEvent.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TraceEvent.java @@ -7,8 +7,6 @@ package org.elasticsearch.xpack.profiling.action; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; final class TraceEvent { @@ -16,7 +14,7 @@ final class TraceEvent { double annualCO2Tons; double annualCostsUSD; long count; - final Map subGroups = new HashMap<>(); + SubGroup subGroups; TraceEvent(String stacktraceID) { this(stacktraceID, 0); diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TransportGetStackTracesAction.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TransportGetStackTracesAction.java index 5467f0c10ccc8..6efab6e99da6f 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TransportGetStackTracesAction.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/TransportGetStackTracesAction.java @@ -254,19 +254,11 @@ private void searchGenericEventGroupedByStackTrace( CountedTermsAggregationBuilder groupByStackTraceId = new CountedTermsAggregationBuilder("group_by").size( MAX_TRACE_EVENTS_RESULT_SIZE ).field(request.getStackTraceIdsField()); - if (request.getAggregationField() != null) { - String aggregationField = request.getAggregationField(); - log.trace("Grouping stacktrace events by [{}].", aggregationField); - // be strict about the accepted field names to avoid downstream errors or leaking unintended information - if (aggregationField.equals("transaction.name") == false) { - throw new IllegalArgumentException( - "Requested custom event aggregation field [" + aggregationField + "] but only [transaction.name] is supported." - ); - } - groupByStackTraceId.subAggregation( - new TermsAggregationBuilder(CUSTOM_EVENT_SUB_AGGREGATION_NAME).field(request.getAggregationField()) - ); - } + SubGroupCollector subGroups = SubGroupCollector.attach( + groupByStackTraceId, + request.getAggregationFields(), + request.isLegacyAggregationField() + ); RandomSamplerAggregationBuilder randomSampler = new RandomSamplerAggregationBuilder("sample").setSeed(request.hashCode()) .setProbability(responseBuilder.getSamplingRate()) .subAggregation(groupByStackTraceId); @@ -307,14 +299,7 @@ private void searchGenericEventGroupedByStackTrace( stackTraceEvents.put(stackTraceID, event); } event.count += count; - if (request.getAggregationField() != null) { - Terms eventSubGroup = stacktraceBucket.getAggregations().get(CUSTOM_EVENT_SUB_AGGREGATION_NAME); - for (Terms.Bucket b : eventSubGroup.getBuckets()) { - String subGroupName = b.getKeyAsString(); - long subGroupCount = event.subGroups.getOrDefault(subGroupName, 0L); - event.subGroups.put(subGroupName, subGroupCount + b.getDocCount()); - } - } + subGroups.collectResults(stacktraceBucket, event); } responseBuilder.setTotalSamples(totalSamples); responseBuilder.setHostEventCounts(hostEventCounts); @@ -340,17 +325,11 @@ private void searchEventGroupedByStackTrace( // Especially with high cardinality fields, this makes aggregations really slow. .executionHint("map") .subAggregation(new SumAggregationBuilder("count").field("Stacktrace.count")); - if (request.getAggregationField() != null) { - String aggregationField = request.getAggregationField(); - log.trace("Grouping stacktrace events by [{}].", aggregationField); - // be strict about the accepted field names to avoid downstream errors or leaking unintended information - if (aggregationField.equals("service.name") == false) { - throw new IllegalArgumentException( - "Requested custom event aggregation field [" + aggregationField + "] but only [service.name] is supported." - ); - } - groupByStackTraceId.subAggregation(new TermsAggregationBuilder(CUSTOM_EVENT_SUB_AGGREGATION_NAME).field(aggregationField)); - } + SubGroupCollector subGroups = SubGroupCollector.attach( + groupByStackTraceId, + request.getAggregationFields(), + request.isLegacyAggregationField() + ); client.prepareSearch(eventsIndex.getName()) .setTrackTotalHits(false) .setSize(0) @@ -412,14 +391,7 @@ The same stacktraces may come from different hosts (eventually from different da stackTraceEvents.put(stackTraceID, event); } event.count += finalCount; - if (request.getAggregationField() != null) { - Terms subGroup = stacktraceBucket.getAggregations().get(CUSTOM_EVENT_SUB_AGGREGATION_NAME); - for (Terms.Bucket b : subGroup.getBuckets()) { - String subGroupName = b.getKeyAsString(); - long subGroupCount = event.subGroups.getOrDefault(subGroupName, 0L); - event.subGroups.put(subGroupName, subGroupCount + b.getDocCount()); - } - } + subGroups.collectResults(stacktraceBucket, event); } } responseBuilder.setTotalSamples(totalFinalCount); diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequestTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequestTests.java index 70bb1abfc40ac..82544f7cb7acf 100644 --- a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequestTests.java +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetStackTracesRequestTests.java @@ -51,6 +51,8 @@ public void testParseValidXContent() throws IOException { // Expect the default values assertNull(request.getIndices()); assertNull(request.getStackTraceIdsField()); + assertFalse(request.isLegacyAggregationField()); + assertNull(request.getAggregationFields()); assertNull(request.getAwsCostFactor()); assertNull(request.getAzureCostFactor()); assertNull(request.getCustomCO2PerKWH()); @@ -90,6 +92,92 @@ public void testParseValidXContentWithCustomIndex() throws IOException { // Expect the default values assertNull(request.getRequestedDuration()); + assertFalse(request.isLegacyAggregationField()); + assertNull(request.getAggregationFields()); + assertNull(request.getAwsCostFactor()); + assertNull(request.getAzureCostFactor()); + assertNull(request.getCustomCO2PerKWH()); + assertNull(request.getCustomDatacenterPUE()); + assertNull(request.getCustomCostPerCoreHour()); + assertNull(request.getCustomPerCoreWattX86()); + assertNull(request.getCustomPerCoreWattARM64()); + } + } + + public void testParseValidXContentWithOneAggregationField() throws IOException { + try (XContentParser content = createParser(XContentFactory.jsonBuilder() + //tag::noformat + .startObject() + .field("sample_size", 2000) + .field("indices", new String[] {"my-traces"}) + .field("stacktrace_ids_field", "stacktraces") + .field("aggregation_field", "service") + .startObject("query") + .startObject("range") + .startObject("@timestamp") + .field("gte", "2022-10-05") + .endObject() + .endObject() + .endObject() + .endObject() + //end::noformat + )) { + + GetStackTracesRequest request = new GetStackTracesRequest(); + request.parseXContent(content); + + assertEquals(2000, request.getSampleSize()); + assertArrayEquals(new String[] { "my-traces" }, request.getIndices()); + assertEquals("stacktraces", request.getStackTraceIdsField()); + assertArrayEquals(new String[] { "service" }, request.getAggregationFields()); + assertTrue(request.isLegacyAggregationField()); + // a basic check suffices here + assertEquals("@timestamp", ((RangeQueryBuilder) request.getQuery()).fieldName()); + + // Expect the default values + assertNull(request.getRequestedDuration()); + assertNull(request.getAwsCostFactor()); + assertNull(request.getAzureCostFactor()); + assertNull(request.getCustomCO2PerKWH()); + assertNull(request.getCustomDatacenterPUE()); + assertNull(request.getCustomCostPerCoreHour()); + assertNull(request.getCustomPerCoreWattX86()); + assertNull(request.getCustomPerCoreWattARM64()); + } + } + + public void testParseValidXContentWithMultipleAggregationFields() throws IOException { + try (XContentParser content = createParser(XContentFactory.jsonBuilder() + //tag::noformat + .startObject() + .field("sample_size", 2000) + .field("indices", new String[] {"my-traces"}) + .field("stacktrace_ids_field", "stacktraces") + .field("aggregation_fields", new String[] {"service", "transaction"}) + .startObject("query") + .startObject("range") + .startObject("@timestamp") + .field("gte", "2022-10-05") + .endObject() + .endObject() + .endObject() + .endObject() + //end::noformat + )) { + + GetStackTracesRequest request = new GetStackTracesRequest(); + request.parseXContent(content); + + assertEquals(2000, request.getSampleSize()); + assertArrayEquals(new String[] { "my-traces" }, request.getIndices()); + assertEquals("stacktraces", request.getStackTraceIdsField()); + assertArrayEquals(new String[] { "service", "transaction" }, request.getAggregationFields()); + // a basic check suffices here + assertEquals("@timestamp", ((RangeQueryBuilder) request.getQuery()).fieldName()); + + // Expect the default values + assertNull(request.getRequestedDuration()); + assertFalse(request.isLegacyAggregationField()); assertNull(request.getAwsCostFactor()); assertNull(request.getAzureCostFactor()); assertNull(request.getCustomCO2PerKWH()); @@ -143,6 +231,8 @@ public void testParseValidXContentWithCustomCostAndCO2Data() throws IOException // Expect the default values assertNull(request.getIndices()); assertNull(request.getStackTraceIdsField()); + assertFalse(request.isLegacyAggregationField()); + assertNull(request.getAggregationFields()); } } @@ -255,6 +345,7 @@ public void testValidateWrongSampleSize() { null, null, null, + null, null ); List validationErrors = request.validate().validationErrors(); @@ -276,6 +367,7 @@ public void testValidateSampleSizeIsValidWithCustomIndices() { null, null, null, + null, null ); assertNull("Expecting no validation errors", request.validate()); @@ -295,6 +387,7 @@ public void testValidateStacktraceWithoutIndices() { null, null, null, + null, null ); List validationErrors = request.validate().validationErrors(); @@ -316,6 +409,7 @@ public void testValidateIndicesWithoutStacktraces() { null, null, null, + null, null ); List validationErrors = request.validate().validationErrors(); @@ -323,6 +417,114 @@ public void testValidateIndicesWithoutStacktraces() { assertEquals("[stacktrace_ids_field] is mandatory", validationErrors.get(0)); } + public void testValidateEmptyAggregationField() { + GetStackTracesRequest request = new GetStackTracesRequest( + null, + 1.0d, + 1.0d, + 1.0d, + null, + new String[] { randomAlphaOfLength(5) }, + randomAlphaOfLength(5), + "", + null, + null, + null, + null, + null, + null + ); + List validationErrors = request.validate().validationErrors(); + assertEquals(1, validationErrors.size()); + assertEquals("[aggregation_field] must be non-empty", validationErrors.get(0)); + } + + public void testValidateAggregationFieldAndAggregationFields() { + GetStackTracesRequest request = new GetStackTracesRequest( + null, + 1.0d, + 1.0d, + 1.0d, + null, + new String[] { randomAlphaOfLength(5) }, + randomAlphaOfLength(5), + "transaction.name", + new String[] { "transaction.name", "service.name" }, + null, + null, + null, + null, + null + ); + List validationErrors = request.validate().validationErrors(); + assertEquals(1, validationErrors.size()); + assertEquals("[aggregation_field] must not be set when [aggregation_fields] is also set", validationErrors.get(0)); + } + + public void testValidateAggregationFieldsContainsTooFewElements() { + GetStackTracesRequest request = new GetStackTracesRequest( + null, + 1.0d, + 1.0d, + 1.0d, + null, + new String[] { randomAlphaOfLength(5) }, + randomAlphaOfLength(5), + null, + new String[] {}, + null, + null, + null, + null, + null + ); + List validationErrors = request.validate().validationErrors(); + assertEquals(1, validationErrors.size()); + assertEquals("[aggregation_fields] must contain either one or two elements but contains [0] elements.", validationErrors.get(0)); + } + + public void testValidateAggregationFieldsContainsTooManyElements() { + GetStackTracesRequest request = new GetStackTracesRequest( + null, + 1.0d, + 1.0d, + 1.0d, + null, + new String[] { randomAlphaOfLength(5) }, + randomAlphaOfLength(5), + null, + new String[] { "application", "service", "transaction" }, + null, + null, + null, + null, + null + ); + List validationErrors = request.validate().validationErrors(); + assertEquals(1, validationErrors.size()); + assertEquals("[aggregation_fields] must contain either one or two elements but contains [3] elements.", validationErrors.get(0)); + } + + public void testValidateAggregationFieldsContainsEnoughElements() { + GetStackTracesRequest request = new GetStackTracesRequest( + null, + 1.0d, + 1.0d, + 1.0d, + null, + new String[] { randomAlphaOfLength(5) }, + randomAlphaOfLength(5), + null, + new String[] { "service", "service" }, + null, + null, + null, + null, + null + ); + assertNull("Expecting no validation errors", request.validate()); + } + public void testConsidersCustomIndicesInRelatedIndices() { String customIndex = randomAlphaOfLength(5); GetStackTracesRequest request = new GetStackTracesRequest( @@ -338,6 +540,7 @@ public void testConsidersCustomIndicesInRelatedIndices() { null, null, null, + null, null ); String[] indices = request.indices(); @@ -359,6 +562,7 @@ public void testConsidersDefaultIndicesInRelatedIndices() { null, null, null, + null, null ); String[] indices = request.indices(); diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java index ebb3d492b024c..24f8ea85212d8 100644 --- a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java @@ -16,7 +16,6 @@ import java.io.IOException; import java.util.List; -import java.util.Map; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; @@ -56,7 +55,13 @@ public void testToXContent() throws IOException { .field("line_number", sourceLine) .field("executable_file_name", exeFilename) .endObject() - .field("sub_groups", Map.of("basket", 7L)) + .startObject("sub_groups") + .startObject("transaction.name") + .startObject("basket") + .field("count", 7L) + .endObject() + .endObject() + .endObject() .field("self_count", 1) .field("total_count", 10) .field("self_annual_co2_tons").rawValue("2.2000") @@ -85,7 +90,7 @@ public void testToXContent() throws IOException { 22.0d, 12.0d, 120.0d, - Map.of("basket", 7L) + SubGroup.root("transaction.name", false).addCount("basket", 7L) ); GetTopNFunctionsResponse response = new GetTopNFunctionsResponse(1, 10, 2.2d, 12.0d, List.of(topNFunction)); response.toXContent(actualResponse, ToXContent.EMPTY_PARAMS); diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/ResamplerTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/ResamplerTests.java index c2537edab6bbd..fec9704dc8c02 100644 --- a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/ResamplerTests.java +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/ResamplerTests.java @@ -43,6 +43,7 @@ public void testNoResamplingNoSampleRateAdjustment() { null, null, null, + null, null ); request.setAdjustSampleCount(false); @@ -72,6 +73,7 @@ public void testNoResamplingButAdjustSampleRate() { null, null, null, + null, null ); request.setAdjustSampleCount(true); @@ -101,6 +103,7 @@ public void testResamplingNoSampleRateAdjustment() { null, null, null, + null, null ); request.setAdjustSampleCount(false); @@ -133,6 +136,7 @@ public void testResamplingNoSampleRateAdjustmentWithQuery() { null, null, null, + null, null ); @@ -162,6 +166,7 @@ public void testResamplingAndSampleRateAdjustment() { null, null, null, + null, null ); request.setAdjustSampleCount(true); diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/SubGroupCollectorTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/SubGroupCollectorTests.java new file mode 100644 index 0000000000000..5d6022f322762 --- /dev/null +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/SubGroupCollectorTests.java @@ -0,0 +1,149 @@ +/* + * 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.profiling.action; + +import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder; +import org.elasticsearch.test.ESTestCase; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.xpack.profiling.action.SubGroupCollector.CUSTOM_EVENT_SUB_AGGREGATION_NAME; + +public class SubGroupCollectorTests extends ESTestCase { + public void testNoAggs() { + TermsAggregationBuilder stackTraces = new TermsAggregationBuilder("stacktraces").field("stacktrace.id"); + TraceEvent traceEvent = new TraceEvent("1"); + + SubGroupCollector collector = SubGroupCollector.attach(stackTraces, new String[0], false); + assertTrue("Sub aggregations attached", stackTraces.getSubAggregations().isEmpty()); + + SubGroupCollector.Bucket currentStackTrace = bucket("1", 5); + collector.collectResults(currentStackTrace, traceEvent); + + assertNull(traceEvent.subGroups); + } + + public void testMultipleAggsInSingleStackTrace() { + TermsAggregationBuilder stackTraces = new TermsAggregationBuilder("stacktraces").field("stacktrace.id"); + TraceEvent traceEvent = new TraceEvent("1"); + + SubGroupCollector collector = SubGroupCollector.attach(stackTraces, new String[] { "service.name", "transaction.name" }, false); + assertFalse("No sub aggregations attached", stackTraces.getSubAggregations().isEmpty()); + + StaticAgg services = new StaticAgg(); + SubGroupCollector.Bucket currentStackTrace = bucket("1", 5, services); + // tag::noformat + services.addBuckets(CUSTOM_EVENT_SUB_AGGREGATION_NAME + "service.name", + bucket("basket", 7L, + agg(CUSTOM_EVENT_SUB_AGGREGATION_NAME + "transaction.name", + bucket("add-to-basket", 4L), + bucket("delete-from-basket", 3L) + ) + ), + bucket("checkout", 4L, + agg(CUSTOM_EVENT_SUB_AGGREGATION_NAME + "transaction.name", + bucket("enter-address", 4L), + bucket("submit-order", 3L) + ) + ) + ); + // end::noformat + + collector.collectResults(currentStackTrace, traceEvent); + + assertNotNull(traceEvent.subGroups); + assertEquals(Long.valueOf(7L), traceEvent.subGroups.getCount("basket")); + assertEquals(Long.valueOf(4L), traceEvent.subGroups.getCount("checkout")); + SubGroup basketTransactionNames = traceEvent.subGroups.getSubGroup("basket").getSubGroup("transaction.name"); + assertEquals(Long.valueOf(4L), basketTransactionNames.getCount("add-to-basket")); + assertEquals(Long.valueOf(3L), basketTransactionNames.getCount("delete-from-basket")); + SubGroup checkoutTransactionNames = traceEvent.subGroups.getSubGroup("checkout").getSubGroup("transaction.name"); + assertEquals(Long.valueOf(4L), checkoutTransactionNames.getCount("enter-address")); + assertEquals(Long.valueOf(3L), checkoutTransactionNames.getCount("submit-order")); + } + + public void testSingleAggInMultipleStackTraces() { + TermsAggregationBuilder stackTraces = new TermsAggregationBuilder("stacktraces").field("stacktrace.id"); + TraceEvent traceEvent = new TraceEvent("1"); + + SubGroupCollector collector = SubGroupCollector.attach(stackTraces, new String[] { "service.name" }, false); + assertFalse("No sub aggregations attached", stackTraces.getSubAggregations().isEmpty()); + + StaticAgg services1 = new StaticAgg(); + SubGroupCollector.Bucket currentStackTrace1 = bucket("1", 5, services1); + services1.addBuckets(CUSTOM_EVENT_SUB_AGGREGATION_NAME + "service.name", bucket("basket", 7L)); + + collector.collectResults(currentStackTrace1, traceEvent); + + StaticAgg services2 = new StaticAgg(); + SubGroupCollector.Bucket currentStackTrace2 = bucket("1", 3, services2); + services2.addBuckets(CUSTOM_EVENT_SUB_AGGREGATION_NAME + "service.name", bucket("basket", 1L), bucket("checkout", 5L)); + + collector.collectResults(currentStackTrace2, traceEvent); + + assertNotNull(traceEvent.subGroups); + assertEquals(Long.valueOf(8L), traceEvent.subGroups.getCount("basket")); + assertEquals(Long.valueOf(5L), traceEvent.subGroups.getCount("checkout")); + } + + private SubGroupCollector.Bucket bucket(String key, long count) { + return bucket(key, count, null); + } + + private SubGroupCollector.Bucket bucket(String key, long count, SubGroupCollector.Agg aggregations) { + return new StaticBucket(key, count, aggregations); + } + + private SubGroupCollector.Agg agg(String name, SubGroupCollector.Bucket... buckets) { + StaticAgg a = new StaticAgg(); + a.addBuckets(name, buckets); + return a; + } + + private static class StaticBucket implements SubGroupCollector.Bucket { + private final String key; + private final long count; + private SubGroupCollector.Agg aggregations; + + private StaticBucket(String key, long count, SubGroupCollector.Agg aggregations) { + this.key = key; + this.count = count; + this.aggregations = aggregations; + } + + @Override + public String getKey() { + return key; + } + + @Override + public long getCount() { + return count; + } + + @Override + public SubGroupCollector.Agg getAggregations() { + return aggregations; + } + } + + private static class StaticAgg implements SubGroupCollector.Agg { + private final Map> buckets = new HashMap<>(); + + public void addBuckets(String name, SubGroupCollector.Bucket... buckets) { + this.buckets.put(name, List.of(buckets)); + } + + @Override + public Iterable getBuckets(String aggName) { + return buckets.get(aggName); + } + } +} diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/SubGroupTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/SubGroupTests.java new file mode 100644 index 0000000000000..c571d7c03c252 --- /dev/null +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/SubGroupTests.java @@ -0,0 +1,102 @@ +/* + * 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.profiling.action; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentType; + +import java.io.IOException; + +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; + +public class SubGroupTests extends ESTestCase { + public void testToXContent() throws IOException { + XContentType contentType = randomFrom(XContentType.values()); + // tag::noformat + XContentBuilder expectedRequest = XContentFactory.contentBuilder(contentType) + .startObject() + .startObject("transaction.name") + .startObject("basket") + .field("count", 7L) + .endObject() + .endObject() + .endObject(); + // end::noformat + + XContentBuilder actualRequest = XContentFactory.contentBuilder(contentType); + actualRequest.startObject(); + SubGroup g = SubGroup.root("transaction.name", false).addCount("basket", 7L); + g.toXContent(actualRequest, ToXContent.EMPTY_PARAMS); + actualRequest.endObject(); + + assertToXContentEquivalent(BytesReference.bytes(expectedRequest), BytesReference.bytes(actualRequest), contentType); + } + + public void testRenderLegacyXContent() throws IOException { + XContentType contentType = randomFrom(XContentType.values()); + // tag::noformat + XContentBuilder expectedRequest = XContentFactory.contentBuilder(contentType) + .startObject() + .field("basket", 7L) + .endObject(); + // end::noformat + + XContentBuilder actualRequest = XContentFactory.contentBuilder(contentType); + actualRequest.startObject(); + SubGroup g = SubGroup.root("transaction.name", true).addCount("basket", 7L); + g.toXContent(actualRequest, ToXContent.EMPTY_PARAMS); + actualRequest.endObject(); + + assertToXContentEquivalent(BytesReference.bytes(expectedRequest), BytesReference.bytes(actualRequest), contentType); + } + + public void testMergeNoCommonRoot() { + SubGroup root1 = SubGroup.root("transaction.name", false); + SubGroup root2 = SubGroup.root("service.name", false); + + SubGroup toMerge = root1.copy(); + + toMerge.merge(root2); + + assertEquals(root1, toMerge); + } + + public void testMergeIdenticalTree() { + SubGroup g = SubGroup.root("transaction.name", false); + g.addCount("basket", 5L); + g.addCount("checkout", 7L); + + SubGroup g2 = g.copy(); + + g.merge(g2); + + assertEquals(Long.valueOf(10L), g.getCount("basket")); + assertEquals(Long.valueOf(14L), g.getCount("checkout")); + } + + public void testMergeMixedTree() { + SubGroup g1 = SubGroup.root("transaction.name", false); + g1.addCount("basket", 5L); + g1.addCount("checkout", 7L); + + SubGroup g2 = SubGroup.root("transaction.name", false); + g2.addCount("catalog", 8L); + g2.addCount("basket", 5L); + g2.addCount("checkout", 2L); + + g1.merge(g2); + + assertEquals(Long.valueOf(8L), g1.getCount("catalog")); + assertEquals(Long.valueOf(10L), g1.getCount("basket")); + assertEquals(Long.valueOf(9L), g1.getCount("checkout")); + } +} diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TopNFunctionTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TopNFunctionTests.java index 9623415b41554..76379adcd3b8a 100644 --- a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TopNFunctionTests.java +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TopNFunctionTests.java @@ -16,7 +16,6 @@ import org.elasticsearch.xcontent.XContentType; import java.io.IOException; -import java.util.Map; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; @@ -35,31 +34,35 @@ public void testToXContent() throws IOException { String frameGroupID = FrameGroupID.create(fileID, addressOrLine, exeFilename, sourceFilename, functionName); XContentType contentType = randomFrom(XContentType.values()); + // tag::noformat XContentBuilder expectedRequest = XContentFactory.contentBuilder(contentType) .startObject() - .field("id", frameGroupID) - .field("rank", 1) - .startObject("frame") - .field("frame_type", frameType) - .field("inline", inline) - .field("address_or_line", addressOrLine) - .field("function_name", functionName) - .field("file_name", sourceFilename) - .field("line_number", sourceLine) - .field("executable_file_name", exeFilename) - .endObject() - .field("sub_groups", Map.of("basket", 7L)) - .field("self_count", 1) - .field("total_count", 10) - .field("self_annual_co2_tons") - .rawValue("2.2000") - .field("total_annual_co2_tons") - .rawValue("22.0000") - .field("self_annual_costs_usd") - .rawValue("12.0000") - .field("total_annual_costs_usd") - .rawValue("120.0000") + .field("id", frameGroupID) + .field("rank", 1) + .startObject("frame") + .field("frame_type", frameType) + .field("inline", inline) + .field("address_or_line", addressOrLine) + .field("function_name", functionName) + .field("file_name", sourceFilename) + .field("line_number", sourceLine) + .field("executable_file_name", exeFilename) + .endObject() + .startObject("sub_groups") + .startObject("transaction.name") + .startObject("basket") + .field("count", 7L) + .endObject() + .endObject() + .endObject() + .field("self_count", 1) + .field("total_count", 10) + .field("self_annual_co2_tons").rawValue("2.2000") + .field("total_annual_co2_tons").rawValue("22.0000") + .field("self_annual_costs_usd").rawValue("12.0000") + .field("total_annual_costs_usd").rawValue("120.0000") .endObject(); + // end::noformat XContentBuilder actualRequest = XContentFactory.contentBuilder(contentType); TopNFunction topNFunction = new TopNFunction( @@ -78,7 +81,7 @@ public void testToXContent() throws IOException { 22.0d, 12.0d, 120.0d, - Map.of("basket", 7L) + SubGroup.root("transaction.name", false).addCount("basket", 7L) ); topNFunction.toXContent(actualRequest, ToXContent.EMPTY_PARAMS); @@ -113,8 +116,8 @@ public void testEquality() { 4.0d, 23.2d, 12.0d, - Map.of("checkout", 4L, "basket", 12L) + SubGroup.root("transaction.name", false).addCount("checkout", 4L).addCount("basket", 12L) ); - EqualsHashCodeTestUtils.checkEqualsAndHashCode(topNFunction, (TopNFunction::clone)); + EqualsHashCodeTestUtils.checkEqualsAndHashCode(topNFunction, (TopNFunction::copy)); } } diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TransportGetTopNFunctionsActionTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TransportGetTopNFunctionsActionTests.java index 6e5ed79579a0f..2fcf961f9b9a5 100644 --- a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TransportGetTopNFunctionsActionTests.java +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/TransportGetTopNFunctionsActionTests.java @@ -9,7 +9,6 @@ import org.elasticsearch.test.ESTestCase; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -165,7 +164,7 @@ private TopNFunction topN( annualCO2TonsInclusive, annualCostsUSDExclusive, annualCostsUSDInclusive, - Collections.emptyMap() + null ); } diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/options/EsSourceOptions.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/options/EsSourceOptions.java deleted file mode 100644 index 25b40b4b447fd..0000000000000 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/options/EsSourceOptions.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.ql.options; - -import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.cluster.routing.Preference; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.xpack.ql.util.StringUtils; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; - -import static org.elasticsearch.action.support.IndicesOptions.ConcreteTargetOptions.IGNORE_UNAVAILABLE; -import static org.elasticsearch.action.support.IndicesOptions.WildcardOptions.ALLOW_NO_INDICES; - -/* - * This provides a repository for index resolution and/or search-time configuration options. - * Such as: [search] preference and [search / index resolution] allow_no_indices, ignore_unavailable. - * - * Some of the options end up in a IndicesOptions instance. However, FieldCaps and Search APIs use IndicesOptions - * defaults having conflicting values. So this class will just validate and record the user-provided settings first, and then apply these - * onto a base (an API-specific default). - */ -public class EsSourceOptions { - - private static final String OPTION_PREFERENCE = "preference"; - public static final EsSourceOptions NO_OPTIONS = new EsSourceOptions(); - - @Nullable - private String allowNoIndices; - @Nullable - private String ignoreUnavailable; - @Nullable - private String preference; - - public EsSourceOptions() {} - - public EsSourceOptions(StreamInput in) throws IOException { - this.allowNoIndices = in.readOptionalString(); - this.ignoreUnavailable = in.readOptionalString(); - this.preference = in.readOptionalString(); - } - - public IndicesOptions indicesOptions(IndicesOptions base) { - if (allowNoIndices == null && ignoreUnavailable == null) { - return base; - } - var wildcardOptions = allowNoIndices != null - ? IndicesOptions.WildcardOptions.parseParameters(null, allowNoIndices, base.wildcardOptions()) - : base.wildcardOptions(); - var targetOptions = ignoreUnavailable != null - ? IndicesOptions.ConcreteTargetOptions.fromParameter(ignoreUnavailable, base.concreteTargetOptions()) - : base.concreteTargetOptions(); - return new IndicesOptions(targetOptions, wildcardOptions, base.gatekeeperOptions(), base.failureStoreOptions()); - } - - @Nullable - public String preference() { - return preference; - } - - public void addOption(String name, String value) { - switch (name) { - case ALLOW_NO_INDICES -> { - requireUnset(name, allowNoIndices); - IndicesOptions.WildcardOptions.parseParameters(null, value, null); - allowNoIndices = value; - } - case IGNORE_UNAVAILABLE -> { - requireUnset(name, ignoreUnavailable); - IndicesOptions.ConcreteTargetOptions.fromParameter(value, null); - ignoreUnavailable = value; - } - case OPTION_PREFERENCE -> { - requireUnset(name, preference); - // The validation applies only for the predefined settings (i.e. prefixed by '_') or empty one (i.e. delegate handling - // of this case). - if (value.isEmpty() || value.charAt(0) == '_') { - // Note: _search will neither fail, nor warn about something like `preference=_shards:0,1|_doesnotexist` - Preference.parse(value); - } - preference = value; - } - default -> { - String message = "unknown option named [" + name + "]"; - List matches = StringUtils.findSimilar(name, List.of(ALLOW_NO_INDICES, IGNORE_UNAVAILABLE, OPTION_PREFERENCE)); - if (matches.isEmpty() == false) { - String suggestions = matches.size() == 1 ? "[" + matches.get(0) + "]" : "any of " + matches; - message += ", did you mean " + suggestions + "?"; - } - throw new IllegalArgumentException(message); - } - } - } - - private static void requireUnset(String name, String value) { - if (value != null) { - throw new IllegalArgumentException("option [" + name + "] has already been provided"); - } - } - - public void writeEsSourceOptions(StreamOutput out) throws IOException { - out.writeOptionalString(allowNoIndices); - out.writeOptionalString(ignoreUnavailable); - out.writeOptionalString(preference); - } - - @Override - public int hashCode() { - return Objects.hash(allowNoIndices, ignoreUnavailable, preference); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - EsSourceOptions other = (EsSourceOptions) obj; - return Objects.equals(allowNoIndices, other.allowNoIndices) - && Objects.equals(ignoreUnavailable, other.ignoreUnavailable) - && Objects.equals(preference, other.preference); - } -} diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/plan/logical/EsRelation.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/plan/logical/EsRelation.java index 94e0177972306..4a31309ac8f2f 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/plan/logical/EsRelation.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/plan/logical/EsRelation.java @@ -9,7 +9,6 @@ import org.elasticsearch.xpack.ql.expression.Attribute; import org.elasticsearch.xpack.ql.expression.FieldAttribute; import org.elasticsearch.xpack.ql.index.EsIndex; -import org.elasticsearch.xpack.ql.options.EsSourceOptions; import org.elasticsearch.xpack.ql.tree.NodeInfo; import org.elasticsearch.xpack.ql.tree.NodeUtils; import org.elasticsearch.xpack.ql.tree.Source; @@ -25,33 +24,26 @@ public class EsRelation extends LeafPlan { private final EsIndex index; private final List attrs; - private final EsSourceOptions esSourceOptions; private final boolean frozen; public EsRelation(Source source, EsIndex index, boolean frozen) { - this(source, index, flatten(source, index.mapping()), EsSourceOptions.NO_OPTIONS, frozen); + this(source, index, flatten(source, index.mapping()), frozen); } public EsRelation(Source source, EsIndex index, List attributes) { - this(source, index, attributes, EsSourceOptions.NO_OPTIONS, false); + this(source, index, attributes, false); } - public EsRelation(Source source, EsIndex index, List attributes, EsSourceOptions esSourceOptions) { - this(source, index, attributes, esSourceOptions, false); - } - - public EsRelation(Source source, EsIndex index, List attributes, EsSourceOptions esSourceOptions, boolean frozen) { + public EsRelation(Source source, EsIndex index, List attributes, boolean frozen) { super(source); this.index = index; this.attrs = attributes; - Objects.requireNonNull(esSourceOptions); - this.esSourceOptions = esSourceOptions; this.frozen = frozen; } @Override protected NodeInfo info() { - return NodeInfo.create(this, EsRelation::new, index, attrs, esSourceOptions, frozen); + return NodeInfo.create(this, EsRelation::new, index, attrs, frozen); } private static List flatten(Source source, Map mapping) { @@ -81,10 +73,6 @@ public EsIndex index() { return index; } - public EsSourceOptions esSourceOptions() { - return esSourceOptions; - } - public boolean frozen() { return frozen; } @@ -101,7 +89,7 @@ public boolean expressionsResolved() { @Override public int hashCode() { - return Objects.hash(index, esSourceOptions, frozen); + return Objects.hash(index, frozen); } @Override @@ -115,7 +103,7 @@ public boolean equals(Object obj) { } EsRelation other = (EsRelation) obj; - return Objects.equals(index, other.index) && Objects.equals(esSourceOptions, other.esSourceOptions) && frozen == other.frozen; + return Objects.equals(index, other.index) && frozen == other.frozen; } @Override diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/AbstractRemoteClusterSecurityTestCase.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/AbstractRemoteClusterSecurityTestCase.java index 2aa96ffc4e443..c590e3d932a9f 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/AbstractRemoteClusterSecurityTestCase.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/AbstractRemoteClusterSecurityTestCase.java @@ -10,7 +10,6 @@ import org.apache.http.HttpHost; import org.apache.http.client.methods.HttpPost; import org.elasticsearch.client.Request; -import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; @@ -293,7 +292,7 @@ protected static Response performRequestAgainstFulfillingCluster(Request request } protected static Response performRequestWithAdminUser(RestClient targetFulfillingClusterClient, Request request) throws IOException { - request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", basicAuthHeaderValue(USER, PASS))); + request.setOptions(request.getOptions().toBuilder().addHeader("Authorization", basicAuthHeaderValue(USER, PASS))); return targetFulfillingClusterClient.performRequest(request); } diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityLegacyCrossClusterApiKeysWithDlsFlsIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityLegacyCrossClusterApiKeysWithDlsFlsIT.java new file mode 100644 index 0000000000000..97fb275c34dd1 --- /dev/null +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityLegacyCrossClusterApiKeysWithDlsFlsIT.java @@ -0,0 +1,402 @@ +/* + * 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.remotecluster; + +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.Strings; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchResponseUtils; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.local.distribution.DistributionType; +import org.elasticsearch.test.cluster.util.resource.Resource; +import org.elasticsearch.test.junit.RunnableTestRuleAdapter; +import org.elasticsearch.xcontent.ObjectPath; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.XContentType; +import org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder; +import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +public class RemoteClusterSecurityLegacyCrossClusterApiKeysWithDlsFlsIT extends AbstractRemoteClusterSecurityTestCase { + + private static final AtomicReference> API_KEY_MAP_REF = new AtomicReference<>(); + private static final AtomicReference> CCR_API_KEY_MAP_REF = new AtomicReference<>(); + private static final AtomicBoolean SSL_ENABLED_REF = new AtomicBoolean(); + private static final AtomicBoolean NODE1_RCS_SERVER_ENABLED = new AtomicBoolean(); + private static final AtomicBoolean NODE2_RCS_SERVER_ENABLED = new AtomicBoolean(); + + private static final String CCR_USER = "ccr_user"; + + static { + fulfillingCluster = ElasticsearchCluster.local() + .distribution(DistributionType.DEFAULT) + .name("fulfilling-cluster") + .nodes(3) + .module("x-pack-ccr") + .apply(commonClusterConfig) + .setting("remote_cluster.port", "0") + .setting("xpack.security.remote_cluster_server.ssl.enabled", () -> String.valueOf(SSL_ENABLED_REF.get())) + .setting("xpack.security.remote_cluster_server.ssl.key", "remote-cluster.key") + .setting("xpack.security.remote_cluster_server.ssl.certificate", "remote-cluster.crt") + .setting("xpack.security.authc.token.enabled", "true") + .keystore("xpack.security.remote_cluster_server.ssl.secure_key_passphrase", "remote-cluster-password") + .node(0, spec -> spec.setting("remote_cluster_server.enabled", "true")) + .node(1, spec -> spec.setting("remote_cluster_server.enabled", () -> String.valueOf(NODE1_RCS_SERVER_ENABLED.get()))) + .node(2, spec -> spec.setting("remote_cluster_server.enabled", () -> String.valueOf(NODE2_RCS_SERVER_ENABLED.get()))) + .build(); + + queryCluster = ElasticsearchCluster.local() + .name("query-cluster") + .apply(commonClusterConfig) + .module("x-pack-ccr") + .setting("xpack.security.remote_cluster_client.ssl.enabled", () -> String.valueOf(SSL_ENABLED_REF.get())) + .setting("xpack.security.remote_cluster_client.ssl.certificate_authorities", "remote-cluster-ca.crt") + .setting("xpack.security.authc.token.enabled", "true") + .keystore("cluster.remote.my_remote_cluster.credentials", () -> { + if (API_KEY_MAP_REF.get() == null) { + final Map apiKeyMap = createCrossClusterAccessApiKey(""" + { + "search": [ + { + "names": ["shared-metrics"] + } + ], + "replication": [ + { + "names": ["shared-metrics"] + } + ] + }"""); + API_KEY_MAP_REF.set(apiKeyMap); + } + return (String) API_KEY_MAP_REF.get().get("encoded"); + }) + .keystore("cluster.remote.my_ccr_cluster.credentials", () -> { + if (CCR_API_KEY_MAP_REF.get() == null) { + final Map apiKeyMap = createCrossClusterAccessApiKey(""" + { + "search": [ + { + "names": ["leader-index", "shared-*", "metrics-*"] + } + ], + "replication": [ + { + "names": ["leader-index", "shared-*", "metrics-*"] + } + ] + }"""); + CCR_API_KEY_MAP_REF.set(apiKeyMap); + } + return (String) CCR_API_KEY_MAP_REF.get().get("encoded"); + }) + .rolesFile(Resource.fromClasspath("roles.yml")) + .user(REMOTE_METRIC_USER, PASS.toString(), "read_remote_shared_metrics", false) + .user(CCR_USER, PASS.toString(), "ccr_user_role", false) + .build(); + } + + @ClassRule + // Use a RuleChain to ensure that fulfilling cluster is started before query cluster + // `SSL_ENABLED_REF` is used to control the SSL-enabled setting on the test clusters + // We set it here, since randomization methods are not available in the static initialize context above + public static TestRule clusterRule = RuleChain.outerRule(new RunnableTestRuleAdapter(() -> { + SSL_ENABLED_REF.set(usually()); + NODE1_RCS_SERVER_ENABLED.set(randomBoolean()); + NODE2_RCS_SERVER_ENABLED.set(randomBoolean()); + })).around(fulfillingCluster).around(queryCluster); + + public void testCrossClusterSearchBlockedIfApiKeyInvalid() throws Exception { + configureRemoteCluster(); + final String crossClusterAccessApiKeyId = (String) API_KEY_MAP_REF.get().get("id"); + + // Fulfilling cluster + { + // Spread the shards to all nodes + final Request createIndexRequest = new Request("PUT", "shared-metrics"); + createIndexRequest.setJsonEntity(""" + { + "settings": { + "number_of_shards": 3, + "number_of_replicas": 0 + } + }"""); + assertOK(performRequestAgainstFulfillingCluster(createIndexRequest)); + + // Index some documents, so we can attempt to search them from the querying cluster + final Request bulkRequest = new Request("POST", "/_bulk?refresh=true"); + bulkRequest.setJsonEntity(Strings.format(""" + { "index": { "_index": "shared-metrics" } } + { "name": "metric1" } + { "index": { "_index": "shared-metrics" } } + { "name": "metric2" } + { "index": { "_index": "shared-metrics" } } + { "name": "metric3" } + { "index": { "_index": "shared-metrics" } } + { "name": "metric4" } + """)); + assertOK(performRequestAgainstFulfillingCluster(bulkRequest)); + } + + // Query cluster -- test searching works (the API key is valid) + { + final var searchRequest = new Request( + "GET", + String.format( + Locale.ROOT, + "/%s:%s/_search?ccs_minimize_roundtrips=%s", + randomFrom("my_remote_cluster", "my_remote_*"), + randomFrom("shared-metrics", "*"), + randomBoolean() + ) + ); + final Response response = performRequestWithRemoteMetricUser(searchRequest); + assertOK(response); + final SearchResponse searchResponse = SearchResponseUtils.parseSearchResponse(responseAsParser(response)); + try { + final List actualIndices = Arrays.stream(searchResponse.getHits().getHits()) + .map(SearchHit::getIndex) + .collect(Collectors.toList()); + assertThat(Set.copyOf(actualIndices), containsInAnyOrder("shared-metrics")); + } finally { + searchResponse.decRef(); + } + } + + // make API key invalid + addDlsQueryToApiKeyDoc(crossClusterAccessApiKeyId); + + // since we updated the API key doc directly, caches need to be clearer manually -- this would also happen during a rolling restart + // to the FC, during an upgrade + assertOK(performRequestAgainstFulfillingCluster(new Request("POST", "/_security/role/*/_clear_cache"))); + assertOK(performRequestAgainstFulfillingCluster(new Request("POST", "/_security/api_key/*/_clear_cache"))); + + // check that GET still works + getCrossClusterApiKeys(crossClusterAccessApiKeyId); + // check that query still works + validateQueryCrossClusterApiKeys(crossClusterAccessApiKeyId); + + { + final var searchRequest = new Request( + "GET", + String.format( + Locale.ROOT, + "/%s:%s/_search?ccs_minimize_roundtrips=%s", + "my_remote_cluster", + "shared-metrics", + randomBoolean() + ) + ); + updateClusterSettings( + Settings.builder().put("cluster.remote.my_remote_cluster.skip_unavailable", Boolean.toString(true)).build() + ); + final var response = performRequestWithRemoteMetricUser(searchRequest); + assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); + String responseJson = EntityUtils.toString(response.getEntity()); + assertThat(responseJson, containsString("\"status\":\"skipped\"")); + assertThat(responseJson, containsString("search does not support document or field level security if replication is assigned")); + + updateClusterSettings( + Settings.builder().put("cluster.remote.my_remote_cluster.skip_unavailable", Boolean.toString(false)).build() + ); + final ResponseException ex = expectThrows(ResponseException.class, () -> performRequestWithRemoteMetricUser(searchRequest)); + assertThat(ex.getResponse().getStatusLine().getStatusCode(), equalTo(400)); + assertThat( + ex.getMessage(), + containsString("search does not support document or field level security if replication is assigned") + ); + } + } + + public void testCrossClusterReplicationBlockedIfApiKeyInvalid() throws Exception { + // TODO improve coverage to test: + // * auto-follow + // * follow successfully, then break key + configureRemoteCluster("my_ccr_cluster"); + final String crossClusterAccessApiKeyId = (String) CCR_API_KEY_MAP_REF.get().get("id"); + + // fulfilling cluster + { + final Request bulkRequest = new Request("POST", "/_bulk?refresh=true"); + bulkRequest.setJsonEntity(Strings.format(""" + { "index": { "_index": "leader-index" } } + { "name": "doc-1" } + { "index": { "_index": "leader-index" } } + { "name": "doc-2" } + { "index": { "_index": "leader-index" } } + { "name": "doc-3" } + { "index": { "_index": "leader-index" } } + { "name": "doc-4" } + { "index": { "_index": "private-index" } } + { "name": "doc-5" } + """)); + assertOK(performRequestAgainstFulfillingCluster(bulkRequest)); + } + + // make API key invalid + addDlsQueryToApiKeyDoc(crossClusterAccessApiKeyId); + // since we updated the API key doc directly, caches need to be clearer manually -- this would also happen during a rolling restart + // to the FC, during an upgrade + assertOK(performRequestAgainstFulfillingCluster(new Request("POST", "/_security/role/*/_clear_cache"))); + assertOK(performRequestAgainstFulfillingCluster(new Request("POST", "/_security/api_key/*/_clear_cache"))); + + // query cluster + { + final String followIndexName = "follower-index"; + final Request putCcrRequest = new Request("PUT", "/" + followIndexName + "/_ccr/follow?wait_for_active_shards=1"); + putCcrRequest.setJsonEntity(""" + { + "remote_cluster": "my_ccr_cluster", + "leader_index": "leader-index" + }"""); + + final ResponseException ex = expectThrows(ResponseException.class, () -> performRequestWithCcrUser(putCcrRequest)); + assertThat( + ex.getMessage(), + containsString("search does not support document or field level security if replication is assigned") + ); + assertThat(ex.getResponse().getStatusLine().getStatusCode(), equalTo(400)); + } + } + + @SuppressWarnings("unchecked") + private void addDlsQueryToApiKeyDoc(String crossClusterAccessApiKeyId) throws IOException { + Map apiKeyAsMap = getCrossClusterApiKeys(crossClusterAccessApiKeyId); + Map roleDescriptors = (Map) apiKeyAsMap.get("role_descriptors"); + Map crossCluster = (Map) roleDescriptors.get("cross_cluster"); + List> indices = (List>) crossCluster.get("indices"); + indices.forEach(index -> { + List privileges = (List) index.get("privileges"); + if (Arrays.equals(privileges.toArray(String[]::new), CrossClusterApiKeyRoleDescriptorBuilder.CCS_INDICES_PRIVILEGE_NAMES)) { + index.put("query", "{\"match_all\": {}}"); + index.put("privileges", List.of("read", "read_cross_cluster", "view_index_metadata")); // ensure privs emulate pre 8.14 + } + }); + crossCluster.put("cluster", List.of("cross_cluster_search", "cross_cluster_replication")); // ensure privs emulate pre 8.14 + XContentBuilder builder = XContentFactory.jsonBuilder(); + builder.startObject(); + builder.field("cross_cluster", crossCluster); + builder.endObject(); + updateApiKey(crossClusterAccessApiKeyId, org.elasticsearch.common.Strings.toString(builder)); + } + + @SuppressWarnings("unchecked") + private Map getCrossClusterApiKeys(String id) throws IOException { + final var request = new Request(HttpGet.METHOD_NAME, "/_security/api_key"); + request.addParameters(Map.of("id", id)); + + Response response = performRequestAgainstFulfillingCluster(request); + Map responseMap = entityAsMap(response); + List> apiKeys = (List>) responseMap.get("api_keys"); + assertThat(apiKeys.size(), equalTo(1)); + assertNotNull(ObjectPath.eval("role_descriptors.cross_cluster", apiKeys.get(0))); + return apiKeys.get(0); + } + + @SuppressWarnings("unchecked") + private void validateQueryCrossClusterApiKeys(String id) throws IOException { + final var request = new Request(HttpGet.METHOD_NAME, "/_security/_query/api_key"); + request.setJsonEntity(Strings.format(""" + { + "query": { + "ids": { + "values": [ + "%s" + ] + } + } + } + """, id)); + + Response response = performRequestAgainstFulfillingCluster(request); + Map responseMap = entityAsMap(response); + assertThat(responseMap.get("total"), equalTo(1)); + assertThat(responseMap.get("count"), equalTo(1)); + List> apiKeys = (List>) responseMap.get("api_keys"); + assertThat(apiKeys.size(), equalTo(1)); + // assumes this method is only called after we manually update the API key doc with the DLS query + String query = ObjectPath.eval("role_descriptors.cross_cluster.indices.0.query", apiKeys.get(0)); + try { + assertThat(query, equalTo("{\"match_all\": {}}")); + } catch (AssertionError e) { + // it's ugly, but the query could be in the 0 or 1 position. + query = ObjectPath.eval("role_descriptors.cross_cluster.indices.1.query", apiKeys.get(0)); + assertThat(query, equalTo("{\"match_all\": {}}")); + } + } + + private static XContentParser getParser(Response response) throws IOException { + final byte[] responseBody = EntityUtils.toByteArray(response.getEntity()); + return XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, responseBody); + } + + static void updateApiKey(String id, String payload) throws IOException { + final Request request = new Request("POST", "/.security/_update/" + id + "?refresh=true"); + request.setJsonEntity(Strings.format(""" + { + "doc": { + "role_descriptors": %s + } + } + """, payload)); + expectWarnings( + request, + "this request accesses system indices: [.security-7]," + + " but in a future major version, direct access to system indices will be prevented by default" + ); + Response response = performRequestAgainstFulfillingCluster(request); + assertOK(response); + } + + private Response performRequestWithRemoteMetricUser(final Request request) throws IOException { + request.setOptions( + RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", headerFromRandomAuthMethod(REMOTE_METRIC_USER, PASS)) + ); + return client().performRequest(request); + } + + static void expectWarnings(Request request, String... expectedWarnings) { + final Set expected = Set.of(expectedWarnings); + RequestOptions options = request.getOptions().toBuilder().setWarningsHandler(warnings -> { + final Set actual = Set.copyOf(warnings); + // Return true if the warnings aren't what we expected; the client will treat them as a fatal error. + return actual.equals(expected) == false; + }).build(); + request.setOptions(options); + } + + private Response performRequestWithCcrUser(final Request request) throws IOException { + request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", basicAuthHeaderValue(CCR_USER, PASS))); + return client().performRequest(request); + } +} diff --git a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java index 1b0d3397daa90..5ae84517202d4 100644 --- a/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java +++ b/x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/apikey/ApiKeyRestIT.java @@ -122,7 +122,7 @@ public void cleanUp() throws IOException { public void testGetApiKeyRoleDescriptors() throws IOException { // First key without assigned role descriptors, i.e. it inherits owner user's permission // This can be achieved by either omitting the role_descriptors field in the request or - // explicitly set it to an empty object + // explicitly set it to an empty object. final Request createApiKeyRequest1 = new Request("POST", "_security/api_key"); if (randomBoolean()) { createApiKeyRequest1.setJsonEntity(""" @@ -1019,8 +1019,7 @@ public void testCreateCrossClusterApiKey() throws IOException { "access": { "search": [ { - "names": [ "metrics" ], - "query": "{\\"term\\":{\\"score\\":42}}" + "names": [ "metrics" ] } ], "replication": [ @@ -1085,7 +1084,6 @@ public void testCreateCrossClusterApiKey() throws IOException { RoleDescriptor.IndicesPrivileges.builder() .indices("metrics") .privileges("read", "read_cross_cluster", "view_index_metadata") - .query("{\"term\":{\"score\":42}}") .build(), RoleDescriptor.IndicesPrivileges.builder() .indices("logs") @@ -1105,7 +1103,6 @@ public void testCreateCrossClusterApiKey() throws IOException { "names": [ "metrics" ], - "query": "{\\"term\\":{\\"score\\":42}}", "allow_restricted_indices": false } ], @@ -1381,6 +1378,67 @@ public void testCrossClusterApiKeyDoesNotAllowDlsFlsForReplication() throws IOEx }""", "replication does not support document or field level security"); } + public void testCrossClusterApiKeyDoesNotAllowDlsFlsForSearchWhenReplicationAssigned() throws IOException { + assertBadCreateCrossClusterApiKeyRequest(""" + { + "name": "key", + "access": { + "search": [ {"names": ["logs"], "query":{"term": {"tag": 42}}} ], + "replication": [ {"names": ["logs"]} ] + } + }""", "search does not support document or field level security if replication is assigned"); + + assertBadCreateCrossClusterApiKeyRequest(""" + { + "name": "key", + "access": { + "search": [ {"names": ["logs"], "field_security": {"grant": ["*"], "except": ["private"]}} ], + "replication": [ {"names": ["logs"]} ] + } + }""", "search does not support document or field level security if replication is assigned"); + + assertBadCreateCrossClusterApiKeyRequest(""" + { + "name": "key", + "access": { + "search": [ { + "names": ["logs"], + "query": {"term": {"tag": 42}}, + "field_security": {"grant": ["*"], "except": ["private"]} + } ], + "replication": [ {"names": ["logs"]} ] + } + }""", "search does not support document or field level security if replication is assigned"); + + assertBadUpdateCrossClusterApiKeyRequest(""" + { + "access": { + "search": [ {"names": ["logs"], "query":{"term": {"tag": 42}}} ], + "replication": [ {"names": ["logs"]} ] + } + }""", "search does not support document or field level security if replication is assigned"); + + assertBadUpdateCrossClusterApiKeyRequest(""" + { + "access": { + "search": [ {"names": ["logs"], "field_security": {"grant": ["*"], "except": ["private"]}} ], + "replication": [ {"names": ["logs"]} ] + } + }""", "search does not support document or field level security if replication is assigned"); + + assertBadUpdateCrossClusterApiKeyRequest(""" + { + "access": { + "search": [ { + "names": ["logs"], + "query": {"term": {"tag": 42}}, + "field_security": {"grant": ["*"], "except": ["private"]} + } ], + "replication": [ {"names": ["logs"]} ] + } + }""", "search does not support document or field level security if replication is assigned"); + } + public void testCrossClusterApiKeyRequiresName() throws IOException { assertBadCreateCrossClusterApiKeyRequest(""" { @@ -1414,8 +1472,7 @@ public void testUpdateCrossClusterApiKey() throws IOException { "access": { "search": [ { - "names": [ "data" ], - "query": "{\\"term\\":{\\"score\\":42}}" + "names": [ "data" ] } ], "replication": [ @@ -1437,7 +1494,6 @@ public void testUpdateCrossClusterApiKey() throws IOException { RoleDescriptor.IndicesPrivileges.builder() .indices("data") .privileges("read", "read_cross_cluster", "view_index_metadata") - .query("{\"term\":{\"score\":42}}") .build(), RoleDescriptor.IndicesPrivileges.builder() .indices("logs") @@ -1457,7 +1513,6 @@ public void testUpdateCrossClusterApiKey() throws IOException { "search": [ { "names": [ "data" ], - "query": "{\\"term\\":{\\"score\\":42}}", "allow_restricted_indices": false } ], @@ -2069,6 +2124,16 @@ private void assertBadCreateCrossClusterApiKeyRequest(String body, String expect assertThat(e.getMessage(), containsString(expectedErrorMessage)); } + private void assertBadUpdateCrossClusterApiKeyRequest(String body, String expectedErrorMessage) throws IOException { + // doesn't matter that `id` does not exist: validation happens before that check + final Request request = new Request("PUT", "/_security/cross_cluster/api_key/id"); + request.setJsonEntity(body); + setUserForRequest(request, MANAGE_SECURITY_USER, END_USER_PASSWORD); + final ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); + assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(400)); + assertThat(e.getMessage(), containsString(expectedErrorMessage)); + } + private Response sendRequestWithRemoteIndices(final Request request, final boolean executeAsRemoteIndicesUser) throws IOException { if (executeAsRemoteIndicesUser) { return sendRequestAsRemoteUser(request); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index 883d7cb8ab103..0b17bdce98858 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -143,9 +143,6 @@ import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; -import static org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder.CCR_CLUSTER_PRIVILEGE_NAMES; -import static org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder.CCS_AND_CCR_CLUSTER_PRIVILEGE_NAMES; -import static org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder.CCS_CLUSTER_PRIVILEGE_NAMES; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.WORKFLOWS_RESTRICTION_VERSION; import static org.elasticsearch.xpack.security.Security.SECURITY_CRYPTO_THREAD_POOL_NAME; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; @@ -1423,22 +1420,28 @@ public void crossClusterApiKeyUsageStats(ActionListener> lis for (ApiKey apiKeyInfo : apiKeyInfos) { assert apiKeyInfo.getType() == ApiKey.Type.CROSS_CLUSTER; assert apiKeyInfo.getRoleDescriptors().size() == 1; - final String[] clusterPrivileges = apiKeyInfo.getRoleDescriptors().iterator().next().getClusterPrivileges(); - if (Arrays.equals(clusterPrivileges, CCS_CLUSTER_PRIVILEGE_NAMES)) { + final List clusterPrivileges = Arrays.asList( + apiKeyInfo.getRoleDescriptors().iterator().next().getClusterPrivileges() + ); + + if (clusterPrivileges.contains("cross_cluster_search") + && clusterPrivileges.contains("cross_cluster_replication") == false) { ccsKeys += 1; - } else if (Arrays.equals(clusterPrivileges, CCR_CLUSTER_PRIVILEGE_NAMES)) { - ccrKeys += 1; - } else if (Arrays.equals(clusterPrivileges, CCS_AND_CCR_CLUSTER_PRIVILEGE_NAMES)) { - ccsCcrKeys += 1; - } else { - final String message = "invalid cluster privileges [" - + Strings.arrayToCommaDelimitedString(clusterPrivileges) - + "] for cross-cluster API key [" - + apiKeyInfo.getId() - + "]"; - assert false : message; - listener.onFailure(new IllegalStateException(message)); - } + } else if (clusterPrivileges.contains("cross_cluster_replication") + && clusterPrivileges.contains("cross_cluster_search") == false) { + ccrKeys += 1; + } else if (clusterPrivileges.contains("cross_cluster_search") + && clusterPrivileges.contains("cross_cluster_replication")) { + ccsCcrKeys += 1; + } else { + final String message = "invalid cluster privileges " + + clusterPrivileges + + " for cross-cluster API key [" + + apiKeyInfo.getId() + + "]"; + assert false : message; + listener.onFailure(new IllegalStateException(message)); + } } listener.onResponse(Map.of("total", apiKeyInfos.size(), "ccs", ccsKeys, "ccr", ccrKeys, "ccs_ccr", ccsCcrKeys)); }, listener::onFailure)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/RoleDescriptorStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/RoleDescriptorStore.java index ae7df99a55302..50a4658c27ee4 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/RoleDescriptorStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/RoleDescriptorStore.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.core.common.IteratingActionListener; +import org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.authz.store.RoleReference; @@ -107,6 +108,19 @@ public void resolveApiKeyRoleReference( || (apiKeyRoleReference.getRoleType() == RoleReference.ApiKeyRoleType.LIMITED_BY && rolesRetrievalResult.getRoleDescriptors().stream().noneMatch(RoleDescriptor::hasRestriction)) : "there should be zero limited-by role descriptors with restriction and no more than one assigned"; + // TODO we need unit tests for edge-cases here, for instance, we need to test the REST API keys are never checked for invalid legacy + // role descriptors + if (apiKeyRoleReference.checkForInvalidLegacyRoleDescriptorsForCrossClusterAccess()) { + try { + CrossClusterApiKeyRoleDescriptorBuilder.checkForInvalidLegacyRoleDescriptors( + apiKeyRoleReference.getApiKeyId(), + roleDescriptors + ); + } catch (IllegalArgumentException e) { + listener.onFailure(e); + return; + } + } listener.onResponse(rolesRetrievalResult); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 25e03c6d87e34..7cc91966f39bd 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -44,6 +44,7 @@ import org.elasticsearch.index.SlowLogFieldProvider; import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; +import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.indices.TestIndexNameExpressionResolver; import org.elasticsearch.license.ClusterStateLicenseService; import org.elasticsearch.license.License; @@ -374,7 +375,8 @@ public void testOnIndexModuleIsNoOpWithSecurityDisabled() throws Exception { () -> true, TestIndexNameExpressionResolver.newInstance(threadPool.getThreadContext()), Collections.emptyMap(), - mock(SlowLogFieldProvider.class) + mock(SlowLogFieldProvider.class), + MapperMetrics.NOOP ); security.onIndexModule(indexModule); // indexReaderWrapper is a SetOnce so if Security#onIndexModule had already set an ReaderWrapper we would get an exception here diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java index fb5a49428887f..22cd2816ec334 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java @@ -3243,13 +3243,28 @@ private CrossClusterApiKeyAccessWithSerialization randomCrossClusterApiKeyAccess { "names": [ "logs*" - ] + ], + "query": { + "term": { + "tag": 42 + } + }, + "field_security": { + "grant": [ + "*" + ], + "except": [ + "private" + ] + } } ] }""", "[{\"cluster\":[\"cross_cluster_search\",\"monitor_enrich\"]," + "\"indices\":[{\"names\":[\"logs*\"]," - + "\"privileges\":[\"read\",\"read_cross_cluster\",\"view_index_metadata\"]}]," + + "\"privileges\":[\"read\",\"read_cross_cluster\",\"view_index_metadata\"]," + + "\"field_security\":{\"grant\":[\"*\"],\"except\":[\"private\"]}," + + "\"query\":\"{\\\"term\\\":{\\\"tag\\\":42}}\"}]," + "\"applications\":[],\"run_as\":[]}]" ), new CrossClusterApiKeyAccessWithSerialization( @@ -3275,20 +3290,7 @@ private CrossClusterApiKeyAccessWithSerialization randomCrossClusterApiKeyAccess { "names": [ "logs*" - ], - "query": { - "term": { - "tag": 42 - } - }, - "field_security": { - "grant": [ - "*" - ], - "except": [ - "private" - ] - } + ] } ], "replication": [ @@ -3301,8 +3303,7 @@ private CrossClusterApiKeyAccessWithSerialization randomCrossClusterApiKeyAccess ] }""", "[{\"cluster\":[\"cross_cluster_search\",\"monitor_enrich\",\"cross_cluster_replication\"]," - + "\"indices\":[{\"names\":[\"logs*\"],\"privileges\":[\"read\",\"read_cross_cluster\",\"view_index_metadata\"]," - + "\"field_security\":{\"grant\":[\"*\"],\"except\":[\"private\"]},\"query\":\"{\\\"term\\\":{\\\"tag\\\":42}}\"}," + + "\"indices\":[{\"names\":[\"logs*\"],\"privileges\":[\"read\",\"read_cross_cluster\",\"view_index_metadata\"]}," + "{\"names\":[\"archive\"],\"privileges\":[\"cross_cluster_replication\",\"cross_cluster_replication_internal\"]," + "\"allow_restricted_indices\":true}],\"applications\":[],\"run_as\":[]}]" ) diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/api_key/50_cross_cluster.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/api_key/50_cross_cluster.yml index eecf1977ca188..a50a197eecf8d 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/api_key/50_cross_cluster.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/api_key/50_cross_cluster.yml @@ -53,14 +53,7 @@ teardown: "access": { "search": [ { - "names": ["logs*"], - "query": { - "term": { "category": "shared" } - }, - "field_security": { - "grant": ["*"], - "except": ["private"] - } + "names": ["logs*"] } ], "replication": [ @@ -124,15 +117,6 @@ teardown: "read_cross_cluster", "view_index_metadata" ], - "field_security": { - "grant": [ - "*" - ], - "except": [ - "private" - ] - }, - "query": "{\"term\":{\"category\":\"shared\"}}", "allow_restricted_indices": false }, { @@ -161,15 +145,6 @@ teardown: "names": [ "logs*" ], - "field_security": { - "grant": [ - "*" - ], - "except": [ - "private" - ] - }, - "query": "{\"term\":{\"category\":\"shared\"}}", "allow_restricted_indices": false } ], diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/profiling/10_basic.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/profiling/10_basic.yml index 2aee382890c56..325e6ca8bda7c 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/profiling/10_basic.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/profiling/10_basic.yml @@ -138,6 +138,62 @@ teardown: - match: {resources.pre_8_9_1_data: false} - match: {resources.has_data: true} +--- +"Test stacktraces agg": + # This test mimics the behavior of the first query of the get stacktraces API. In certain environments we have + # observed that the stacktrace API returns a count of 2, although we only ever add one document with a count of 1. + # As these failures are very rare we start by adding a minimal reproduction that only relies on core ES features + # and eliminates any use of profiling-related APIs. + - do: + search: + index: profiling-events-all + body: + "query": { + "bool": { + "filter": [ + { + "range": { + "@timestamp": { + "gte": "2023-11-20", + "lt": "2023-11-21", + "format": "yyyy-MM-dd" + } + } + } + ] + } + } + size: 0 + track_total_hits: false + aggs: + group_by: + terms: + field: host.id + execution_hint: map + size: 150000 + aggs: + group_by: + terms: + field: Stacktrace.id + execution_hint: map + size: 150000 + aggs: + count: + sum: + field: Stacktrace.count + total_count: + sum: + field: Stacktrace.count + min_time: + min: + field: "@timestamp" + max_time: + max: + field: "@timestamp" + + - match: { aggregations.group_by.buckets.0.group_by.buckets.0.key: "S07KmaoGhvNte78xwwRbZQ" } + - match: { aggregations.group_by.buckets.0.group_by.buckets.0.count.value: 1 } + --- "Test get stacktraces": - do: diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java index bee2d6aa22355..70896a67a9468 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.index.SlowLogFieldProvider; import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; +import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.indices.TestIndexNameExpressionResolver; import org.elasticsearch.plugins.Plugin; @@ -68,7 +69,8 @@ public void testWatcherDisabledTests() throws Exception { () -> true, TestIndexNameExpressionResolver.newInstance(), Collections.emptyMap(), - mock(SlowLogFieldProvider.class) + mock(SlowLogFieldProvider.class), + MapperMetrics.NOOP ); // this will trip an assertion if the watcher indexing operation listener is null (which it is) but we try to add it watcher.onIndexModule(indexModule); diff --git a/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java b/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java index 98f5daec730bb..07d4491daedf6 100644 --- a/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java +++ b/x-pack/plugin/wildcard/src/test/java/org/elasticsearch/xpack/wildcard/mapper/WildcardFieldMapperTests.java @@ -60,6 +60,7 @@ import org.elasticsearch.index.mapper.LuceneDocument; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperBuilderContext; +import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperTestCase; import org.elasticsearch.index.mapper.Mapping; import org.elasticsearch.index.mapper.MappingLookup; @@ -1107,7 +1108,8 @@ protected final SearchExecutionContext createMockContext() { null, () -> true, null, - emptyMap() + emptyMap(), + MapperMetrics.NOOP ) { @Override public MappedFieldType getFieldType(String name) {