diff --git a/.gitignore b/.gitignore index 8b2da4dc0832a..cac5a799012e1 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ testfixtures_shared/ # Generated checkstyle_ide.xml x-pack/plugin/esql/src/main/generated-src/generated/ +server/src/main/resources/transport/defined/manifest.txt # JEnv .java-version diff --git a/build-tools-internal/build.gradle b/build-tools-internal/build.gradle index c04ba9b90d5e7..0b81ab90ceced 100644 --- a/build-tools-internal/build.gradle +++ b/build-tools-internal/build.gradle @@ -220,6 +220,14 @@ gradlePlugin { id = 'elasticsearch.internal-yaml-rest-test' implementationClass = 'org.elasticsearch.gradle.internal.test.rest.InternalYamlRestTestPlugin' } + transportVersionManagementPlugin { + id = 'elasticsearch.transport-version-management' + implementationClass = 'org.elasticsearch.gradle.internal.transport.TransportVersionManagementPlugin' + } + globalTransportVersionManagementPlugin { + id = 'elasticsearch.global-transport-version-management' + implementationClass = 'org.elasticsearch.gradle.internal.transport.GlobalTransportVersionManagementPlugin' + } } } diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java index b6f4c99e3d0e6..f89eb3131bc7f 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BaseInternalPluginBuildPlugin.java @@ -15,6 +15,7 @@ import org.elasticsearch.gradle.internal.info.BuildParameterExtension; import org.elasticsearch.gradle.internal.precommit.JarHellPrecommitPlugin; import org.elasticsearch.gradle.internal.test.ClusterFeaturesMetadataPlugin; +import org.elasticsearch.gradle.internal.transport.TransportVersionManagementPlugin; import org.elasticsearch.gradle.plugin.PluginBuildPlugin; import org.elasticsearch.gradle.plugin.PluginPropertiesExtension; import org.elasticsearch.gradle.util.GradleUtils; @@ -36,6 +37,7 @@ public void apply(Project project) { project.getPluginManager().apply(JarHellPrecommitPlugin.class); project.getPluginManager().apply(ElasticsearchJavaPlugin.class); project.getPluginManager().apply(ClusterFeaturesMetadataPlugin.class); + project.getPluginManager().apply(TransportVersionManagementPlugin.class); boolean isCi = project.getRootProject().getExtensions().getByType(BuildParameterExtension.class).getCi(); // Clear default dependencies added by public PluginBuildPlugin as we add our // own project dependencies for internal builds diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java new file mode 100644 index 0000000000000..76d0d48f0db1f --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/CollectTransportVersionReferencesTask.java @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashSet; +import java.util.Set; + +/** + * This task locates all method invocations of org.elasticsearch.TransportVersion#fromName(java.lang.String) in the + * provided directory, and then records the value of string literals passed as arguments. It then records each + * string on a newline along with path and line number in the provided output file. + */ +@CacheableTask +public abstract class CollectTransportVersionReferencesTask extends DefaultTask { + public static final String TRANSPORT_VERSION_SET_CLASS = "org/elasticsearch/TransportVersion"; + public static final String TRANSPORT_VERSION_SET_METHOD_NAME = "fromName"; + public static final String CLASS_EXTENSION = ".class"; + public static final String MODULE_INFO = "module-info.class"; + + /** + * The directory to scan for method invocations. + */ + @Classpath + public abstract ConfigurableFileCollection getClassPath(); + + /** + * The output file, with each newline containing the string literal argument of each method + * invocation. + */ + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + @TaskAction + public void checkTransportVersion() throws IOException { + var results = new HashSet(); + + for (var cpElement : getClassPath()) { + Path file = cpElement.toPath(); + if (Files.isDirectory(file)) { + addNamesFromClassesDirectory(results, file); + } + } + + Path outputFile = getOutputFile().get().getAsFile().toPath(); + Files.writeString(outputFile, String.join("\n", results.stream().map(Object::toString).sorted().toList())); + } + + private void addNamesFromClassesDirectory(Set results, Path file) throws IOException { + Files.walkFileTree(file, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String filename = file.getFileName().toString(); + if (filename.endsWith(CLASS_EXTENSION) && filename.endsWith(MODULE_INFO) == false) { + try (var inputStream = Files.newInputStream(file)) { + addNamesFromClass(results, inputStream, classname(file.toString())); + } + } + return FileVisitResult.CONTINUE; + } + }); + } + + private void addNamesFromClass(Set results, InputStream classBytes, String classname) + throws IOException { + ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodNode(Opcodes.ASM9, access, name, descriptor, signature, exceptions) { + int lineNumber = -1; + + @Override + public void visitLineNumber(int line, Label start) { + lineNumber = line; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + if (owner.equals(TRANSPORT_VERSION_SET_CLASS) && name.equals(TRANSPORT_VERSION_SET_METHOD_NAME)) { + var abstractInstruction = this.instructions.getLast(); + String location = classname + " line " + lineNumber; + if (abstractInstruction instanceof LdcInsnNode ldcInsnNode + && ldcInsnNode.cst instanceof String tvName + && tvName.isEmpty() == false) { + results.add(new TransportVersionUtils.TransportVersionReference(tvName, location)); + } else { + // The instruction is not a LDC with a String constant (or an empty String), which is not allowed. + throw new RuntimeException( + "TransportVersion.fromName must be called with a non-empty String literal. " + "See " + location + "." + ); + } + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + }; + } + }; + ClassReader classReader = new ClassReader(classBytes); + classReader.accept(classVisitor, 0); + } + + private static String classname(String filename) { + return filename.substring(0, filename.length() - CLASS_EXTENSION.length()).replaceAll("[/\\\\]", "."); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionDefinitionTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionDefinitionTask.java new file mode 100644 index 0000000000000..777855de93d22 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionDefinitionTask.java @@ -0,0 +1,211 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.elasticsearch.gradle.VersionProperties; +import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.MajorMinor; +import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionLatest; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.options.Option; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdIncrement.PATCH; +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.IdIncrement.SERVER; + +/** + * This task generates transport version definition files. These files + * are runtime resources that TransportVersion loads statically. + * They contain a comma separated list of integer ids. Each file is named the same + * as the transport version name itself (with the .csv suffix). + */ +public abstract class GenerateTransportVersionDefinitionTask extends DefaultTask { + + /** + * Specifies the directory in which contains all TransportVersionSet data files. + * + * @return + */ + @InputDirectory + public abstract DirectoryProperty getTransportResourcesDirectory(); // The plugin should always set this, not optional + + // assumption: this task is always run on main, so we can determine the name by diffing with main and looking for new files added in the + // definition directory. (not true: once we generate the file, this will no longer hold true if we then need to update it) + /** + * Used to set the name of the TransportVersionSet for which a data file will be generated. + */ + @Input + @Optional + @Option(option = "name", description = "TBD") + public abstract Property getTransportVersionName(); // The plugin should always set this, not optional + + /** + * Used to set the `major.minor` release version for which the specific TransportVersion ID will be generated. + * E.g.: "9.2", "8.18", etc. + */ + @Optional + @Input + @Option(option = "versions", description = "The minor version(s) for which to generate IDs, e.g. --versions=\"9.2,9.1\"") + public abstract ListProperty getMinorVersions(); + + // @Optional + // @Input + // public abstract Property> getIdIncrementSupplier(); + + @TaskAction + public void generateTransportVersionData() throws IOException { + getLogger().lifecycle("Name: " + getTransportVersionName().get()); + getLogger().lifecycle("Versions: " + getMinorVersions().get()); + Path resourcesDir = Objects.requireNonNull(getTransportResourcesDirectory().getAsFile().get()).toPath(); + String name = getTransportVersionName().isPresent() ? getTransportVersionName().get() : findLocalTransportVersionName(); + Set targetMinorVersions = new HashSet<>( + getMinorVersions().isPresent() + ? getMinorVersions().get().stream().map(MajorMinor::of).collect(Collectors.toSet()) + : findTargetMinorVersions() + ); + + List ids = new ArrayList<>(); + for (MajorMinor minorVersion : getKnownMinorVersions(resourcesDir)) { + TransportVersionLatest latest = TransportVersionUtils.readLatestFile(resourcesDir, minorVersion); + TransportVersionLatest newLatest = null; + + if (name.equals(latest.name())) { + if (targetMinorVersions.contains(minorVersion) == false) { + // Regenerate to make this operation idempotent. Need to undo prior updates to the latest files if the list of minor + // versions has changed. + } + } else { + if (targetMinorVersions.contains(minorVersion)) { + // increment + ids.add(incrementTVId(latest.id(), minorVersion)); + } + } + + if (newLatest != null) { + TransportVersionUtils.updateLatestFile(resourcesDir, minorVersion.toString(), newLatest.name(), newLatest.id()); + } + } + + /* + final String tvName = Objects.requireNonNull(getTransportVersionName().get()); + List minorVersions = getMinorVersions().get(); + // final var idIncrementSupplier = Objects.requireNonNull(getIdIncrementSupplier().get()); + + // TODO + // - [x] do we need to also validate that the minorVersions don't contain duplicates here? How do we enforce idempotency if we don't? + // - is there an order we need to apply? ( I don't think so) + // - Do we need to run this iteratively for backport construction, rather than accepting a list like this? (I don't think so) + // - [x] parse args if run alone + // - check that duplicate versions don't come in? + // - Check that we don't have duplicate names (elsewhere, not here) + // - Do we need to allow creating only patch versions? + // - Must also keep data in sync for removal. + // - We could remove any TVs not associated with a version arg. We then either generate or keep any tvs + // for each version arg, and discard the rest + // - How will this work for follow-up backport PRs that will not have all the version labels? + // - The follow up PR somehow needs to know original IDs. Look at git? Need a new task? + // - + + // Load the tvSetData for the specified name, if it exists + final var tvDefinition = getDefinedFile(tvDataDir, tvName); + boolean tvDefinitionExists = tvDefinition != null; + final List preexistingIds = tvDefinitionExists ? Collections.unmodifiableList(tvDefinition.ids()) : List.of(); + + List ids = new ArrayList<>(); + for (var forVersion : forMinorVersions.stream().map(MajorMinor::of).toList()) { + // Get the latest transport version data for the specified minor version. + final int latestTV = getLatestId(tvDataDir, forVersion.toString()); + + // Create the new version id + // final int newID = idIncrementSupplier.apply(forVersion).bumpTransportVersion(latestTV); + final int newID = incrementTVId(latestTV, forVersion); + + // Check that if we already have a TV ID for this minor version + Integer preexistingTVId = retrieveValueInRange( + getPriorLatestId(tvDataDir, forVersion.toString()), + newID, preexistingIds + ); + if (preexistingTVId != null) { + ids.add(preexistingTVId); + // TODO: Should we log something here? + } else { + ids.add(newID); + // Update the LATEST file. + // TODO need to revert the latest files for anything that has been removed. + updateLatestFile(tvDataDir, forVersion.toString(), tvName, newID); + } + } + + writeDefinitionFile(tvDataDir, tvName, ids.stream().sorted(Comparator.reverseOrder()).toList()); + */ + } + + private int incrementTVId(int tvID, MajorMinor version) { + // We can only run this task on main, so the ElasticsearchVersion will be for main. + final var mainVersion = MajorMinor.of(VersionProperties.getElasticsearchVersion()); + final var isMain = version.equals(mainVersion); + if (isMain) { + return SERVER.bumpTransportVersion(tvID); + } else { + return PATCH.bumpTransportVersion(tvID); + } + // TODO add serverless check + } + + private boolean containsValueInRange(int lowerExclusive, int upperInclusive, List ids) { + for (var id : ids) { + if (lowerExclusive < id && id <= upperInclusive) { + return true; + } + } + return false; + } + + private Integer retrieveValueInRange(int lowerExclusive, int upperInclusive, List ids) { + for (var id : ids) { + if (lowerExclusive < id && id <= upperInclusive) { + return id; + } + } + return null; + } + + private List getKnownMinorVersions(Path resourcesDir) { + // list files under latest + return List.of(); + } + + private String findLocalTransportVersionName() { + // check for missing + // if none missing, look at git diff against main + return ""; + } + + private List findTargetMinorVersions() { + // look for env var indicating github PR link from CI + // use github api to find current labels, filter down to version labels + // map version labels to branches + return List.of(); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java new file mode 100644 index 0000000000000..b568214aff77d --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GenerateTransportVersionManifestTask.java @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public abstract class GenerateTransportVersionManifestTask extends DefaultTask { + @InputDirectory + public abstract DirectoryProperty getDefinitionsDirectory(); + + @OutputFile + public abstract RegularFileProperty getManifestFile(); + + @TaskAction + public void generateTransportVersionManifest() throws IOException { + Path constantsDir = getDefinitionsDirectory().get().getAsFile().toPath(); + Path manifestFile = getManifestFile().get().getAsFile().toPath(); + try (var writer = Files.newBufferedWriter(manifestFile)) { + try (var stream = Files.list(constantsDir)) { + for (String filename : stream.map(p -> p.getFileName().toString()).toList()) { + writer.write(filename + "\n"); + } + } + } + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.java new file mode 100644 index 0000000000000..612c6bb13780e --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/GlobalTransportVersionManagementPlugin.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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.Copy; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +import java.util.Map; + +public class GlobalTransportVersionManagementPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(LifecycleBasePlugin.class); + + DependencyHandler depsHandler = project.getDependencies(); + Configuration tvReferencesConfig = project.getConfigurations().create("globalTvReferences"); + tvReferencesConfig.setCanBeConsumed(false); + tvReferencesConfig.setCanBeResolved(true); + tvReferencesConfig.attributes(TransportVersionUtils::addTransportVersionReferencesAttribute); + + // iterate through all projects, and if the management plugin is applied, add that project back as a dep to check + for (Project subProject : project.getRootProject().getSubprojects()) { + subProject.getPlugins().withType(TransportVersionManagementPlugin.class).configureEach(plugin -> { + tvReferencesConfig.getDependencies().add(depsHandler.project(Map.of("path", subProject.getPath()))); + }); + } + + var validateTask = project.getTasks() + .register("validateTransportVersionDefinitions", ValidateTransportVersionDefinitionsTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Validates that all defined TransportVersion constants are used in at least one project"); + t.getTransportResourcesDirectory().set(TransportVersionUtils.getTransportResourcesDirectory(project)); + t.getReferencesFiles().setFrom(tvReferencesConfig); + }); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(t -> t.dependsOn(validateTask)); + + var generateManifestTask = project.getTasks() + .register("generateTransportVersionManifest", GenerateTransportVersionManifestTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Generate a manifest resource for all the known transport version definitions"); + t.getDefinitionsDirectory().set(TransportVersionUtils.getTransportDefinitionsDirectory(project)); + t.getManifestFile().set(project.getLayout().getBuildDirectory().file("generated-resources/manifest.txt")); + }); + project.getTasks().named(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, Copy.class).configure(t -> { + t.into("transport/defined", c -> c.from(generateManifestTask)); + }); + + project.getTasks().register("generateTransportVersionDefinition", GenerateTransportVersionDefinitionTask.class, t -> { + t.getTransportResourcesDirectory().set(TransportVersionUtils.getTransportResourcesDirectory(project)); + }); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java new file mode 100644 index 0000000000000..5020292c5be34 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionManagementPlugin.java @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.elasticsearch.gradle.util.GradleUtils; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.tasks.SourceSet; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +public class TransportVersionManagementPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(LifecycleBasePlugin.class); + + var collectTask = project.getTasks() + .register("collectTransportVersionReferences", CollectTransportVersionReferencesTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Collects all TransportVersion references used throughout the project"); + SourceSet mainSourceSet = GradleUtils.getJavaSourceSets(project).findByName(SourceSet.MAIN_SOURCE_SET_NAME); + t.getClassPath().setFrom(mainSourceSet.getOutput()); + t.getOutputFile().set(project.getLayout().getBuildDirectory().file("transport-version/references.txt")); + }); + + Configuration tvReferencesConfig = project.getConfigurations().create("transportVersionReferences", c -> { + c.setCanBeConsumed(true); + c.setCanBeResolved(false); + c.attributes(TransportVersionUtils::addTransportVersionReferencesAttribute); + }); + project.getArtifacts().add(tvReferencesConfig.getName(), collectTask); + + var validateTask = project.getTasks() + .register("validateTransportVersionReferences", ValidateTransportVersionReferencesTask.class, t -> { + t.setGroup("Transport Versions"); + t.setDescription("Validates that all TransportVersion references used in the project have an associated definition file"); + t.getDefinitionsDirectory().set(TransportVersionUtils.getTransportDefinitionsDirectory(project)); + t.getReferencesFile().set(collectTask.get().getOutputFile()); + }); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(t -> t.dependsOn(validateTask)); + } +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionUtils.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionUtils.java new file mode 100644 index 0000000000000..20fa7737ce66d --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/TransportVersionUtils.java @@ -0,0 +1,232 @@ +/* +* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +* or more contributor license agreements. Licensed under the "Elastic License +* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side +* Public License v 1"; you may not use this file except in compliance with, at +* your election, the "Elastic License 2.0", the "GNU Affero General Public +* License v3.0 only", or the "Server Side Public License, v 1". +*/ + +package org.elasticsearch.gradle.internal.transport; + +import com.google.common.collect.Comparators; + +import org.elasticsearch.gradle.Version; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.file.Directory; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE; + +class TransportVersionUtils { + static final Attribute TRANSPORT_VERSION_REFERENCES_ATTRIBUTE = Attribute.of("transport-version-references", Boolean.class); + + static final String LATEST_DIR = "latest"; + static final String DEFINED_DIR = "defined"; + + private static final String CSV_SUFFIX = ".csv"; + + record TransportVersionDefinition(String name, List ids) { + String path(Path resourcesDir) { + return resourcesDir.resolve(DEFINED_DIR).resolve(name + CSV_SUFFIX).toString(); + } + } + + record TransportVersionLatest(MajorMinor version, String name, int id) { + String path(Path resourcesDir) { + return resourcesDir.resolve(LATEST_DIR).resolve(version.toString() + CSV_SUFFIX).toString(); + } + } + + record TransportVersionReference(String name, String location) { + @Override + public @NotNull String toString() { + return name + " " + location; + } + } + + static int getLatestId(Path resourcesDir, String majorMinor) throws IOException { + return readLatestFile(resourcesDir, MajorMinor.of(majorMinor)).id(); + } + + static TransportVersionLatest readLatestFile(Path resourcesDir, MajorMinor version) throws IOException { + Path filePath = resourcesDir.resolve(LATEST_DIR).resolve(version.toString() + CSV_SUFFIX); + String[] parts = Files.readString(filePath, StandardCharsets.UTF_8).split(","); + assert parts.length == 2; + return new TransportVersionLatest(version, parts[0], Integer.parseInt(parts[1])); + } + + static TransportVersionDefinition readDefinitionFile(Path resourcesDir, String name) { + validateNameFormat(name); + var filePath = resourcesDir.resolve(DEFINED_DIR).resolve(name + CSV_SUFFIX); + if (Files.isRegularFile(filePath) == false) { + System.out.println("Potato file was not found at " + filePath); + return null; + } + try { + String[] parts = Files.readString(filePath, StandardCharsets.UTF_8).split(","); + List ids = Arrays.stream(parts).map(rawId -> Integer.parseInt(rawId.strip())).toList(); + + if (ids.isEmpty()) { + throw new IllegalStateException("Invalid transport version data file [" + filePath + "], no ids"); + } + if (Comparators.isInOrder(ids, Comparator.reverseOrder()) == false) { + throw new IllegalStateException("Invalid transport version data file [" + filePath + "], ids are not in sorted"); + } + return new TransportVersionDefinition(name, ids); + } catch (IOException e) { + throw new UncheckedIOException("Unable to read definition file", e); + } + } + + static Stream readAllDefinitionFiles(Path resourcesDir) throws IOException { + var definitionsStream = Files.list(resourcesDir.resolve(DEFINED_DIR)); + return definitionsStream.map(path -> { + String fileName = path.getFileName().toString(); + assert fileName.endsWith(CSV_SUFFIX); + String name = fileName.substring(0, fileName.length() - 4); + System.out.println("Potato path.getparent" + path.getParent()); + return readDefinitionFile(resourcesDir, name); + }); + } + + static void writeDefinitionFile(Path resourcesDir, String name, List ids) throws IOException { + validateNameFormat(name); + assert ids != null && ids.isEmpty() == false : "Ids must be non-empty"; + Files.writeString( + resourcesDir.resolve(DEFINED_DIR).resolve(name + CSV_SUFFIX), + ids.stream().map(String::valueOf).collect(Collectors.joining(",")) + "\n", + StandardCharsets.UTF_8 + ); + } + + static void updateLatestFile(Path resourcesDir, String majorMinor, String name, int id) throws IOException { + validateNameFormat(name); + var path = resourcesDir.resolve(LATEST_DIR).resolve(majorMinor + CSV_SUFFIX); + assert Files.isRegularFile(path) : "\"Latest\" file was not found at" + path + ", but is required: "; + Files.writeString(path, name + "," + id + "\n", StandardCharsets.UTF_8); + } + + static void validateNameFormat(String name) { + if (Pattern.compile("^\\w+$").matcher(name).matches() == false) { + throw new GradleException("The TransportVersion name must only contain underscores and alphanumeric characters."); + } + } + + static List readReferencesFile(Path file) throws IOException { + assert file.endsWith(".txt"); + List results = new ArrayList<>(); + for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) { + String[] parts = line.split(" ", 2); + if (parts.length != 2) { + throw new IOException("Invalid transport version data file [" + file + "]: " + line); + } + results.add(new TransportVersionReference(parts[0], parts[1])); + } + return results; + } + + static Directory getTransportDefinitionsDirectory(Project project) { + return getTransportResourcesDirectory(project).dir("defined"); + } + + static Directory getTransportResourcesDirectory(Project project) { + var projectName = project.findProperty("org.elasticsearch.transport.definitionsProject"); + if (projectName == null) { + projectName = ":server"; + } + Directory serverDir = project.getRootProject().project(projectName.toString()).getLayout().getProjectDirectory(); + return serverDir.dir("src/main/resources/transport"); + } + + static void addTransportVersionReferencesAttribute(AttributeContainer attributes) { + attributes.attribute(ARTIFACT_TYPE_ATTRIBUTE, "txt"); + attributes.attribute(TransportVersionUtils.TRANSPORT_VERSION_REFERENCES_ATTRIBUTE, true); + } + + /** + * Specifies which part of the TransportVersion id to bump. The TV format: + *

+ * MM_NNN_S_PP + *

+ * M - The major version of Elasticsearch + * NNN - The server version part + * S - The subsidiary version part. It should always be 0 here, it is only used in subsidiary repositories. + * PP - The patch version part + */ + public enum IdIncrement { + MAJOR(1_000_0_00, 2), + SERVER(1_0_00, 3), + SUBSIDIARY(1_00, 1), + PATCH(1, 2); + + private final int value; + private final int max; + + IdIncrement(int value, int numDigits) { + this.value = value; + this.max = (int) Math.pow(10, numDigits); + } + + public int bumpTransportVersion(int tvIDToBump) { + int zeroesCleared = (tvIDToBump / value) * value; + int newId = zeroesCleared + value; + if ((newId / value) % max == 0) { + throw new IllegalStateException( + "Insufficient" + name() + " version section in TransportVersion: " + tvIDToBump + ", Cannot bump." + ); + } + return newId; + } + } + + public record MajorMinor(int major, int minor) { + public static MajorMinor of(String majorMinor) { + String[] versionParts = majorMinor.split("\\."); + assert versionParts.length == 2; + return new MajorMinor(Integer.parseInt(versionParts[0]), Integer.parseInt(versionParts[1])); + } + + public static MajorMinor of(Version version) { + return new MajorMinor(version.getMajor(), version.getMinor()); + } + + @Override + public @NotNull String toString() { + return major + "." + minor; + } + } + + public static int getPriorLatestId(Path dataDir, String majorMinor) throws IOException { + var version = MajorMinor.of(majorMinor); + if (version.minor() > 0) { + return getLatestId(dataDir, version.major + "." + (version.minor - 1)); + } + try (var pathStream = Files.list(Objects.requireNonNull(dataDir.resolve(LATEST_DIR)))) { + var highestMinorOfPrevMajor = pathStream.flatMap(path -> { + var fileMajorMinor = path.getFileName().toString().replace(CSV_SUFFIX, ""); + var fileVersion = MajorMinor.of(fileMajorMinor); + return fileVersion.major == version.major - 1 ? Stream.of(fileVersion.minor) : Stream.empty(); + }).sorted().toList().getLast(); + return getLatestId(dataDir, (version.major - 1) + "." + highestMinorOfPrevMajor); + } + } + +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.java new file mode 100644 index 0000000000000..86773ed94e7f4 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionDefinitionsTask.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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import com.google.common.collect.Comparators; + +import org.elasticsearch.gradle.internal.transport.TransportVersionUtils.TransportVersionReference; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readAllDefinitionFiles; +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.readReferencesFile; +import static org.elasticsearch.gradle.internal.transport.TransportVersionUtils.validateNameFormat; + +/** + * Validates that each defined transport version definition file is referenced by at least one project. + */ +@CacheableTask +public abstract class ValidateTransportVersionDefinitionsTask extends DefaultTask { + + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getTransportResourcesDirectory(); + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public abstract ConfigurableFileCollection getReferencesFiles(); + + @TaskAction + public void validateTransportVersions() throws IOException { + Path resourcesDir = getTransportResourcesDirectory().getAsFile().get().toPath(); + + Set allTvNames = new HashSet<>(); + for (var tvReferencesFile : getReferencesFiles()) { + readReferencesFile(tvReferencesFile.toPath()).stream().map(TransportVersionReference::name).forEach(allTvNames::add); + } + + // TODO validate that all files: + // - have only have a single ID per release version + // - [x] have TVs in order + // - [x] have a name in the correct format + // - have the correct data format + // - [x] Don't have duplicate IDs across any files + // - no duplicate names? Should be impossible due to filename conflicts + + HashSet seenIds = new HashSet<>(); + try (var allDefinitions = readAllDefinitionFiles(resourcesDir)) { + allDefinitions.forEach(definition -> { + // Validate that all definitions are referenced in the codebase: + if (allTvNames.contains(definition.name()) == false) { + throw new IllegalStateException( + "Transport version definition file " + definition.path(resourcesDir) + " is not referenced in the codebase." + ); + } + + // Validate that all Ids are in decending order: + if (Comparators.isInOrder(definition.ids(), Comparator.reverseOrder()) == false) { + throw new IllegalStateException( + "Transport version definition file " + definition.path(resourcesDir) + " does not have ordered ids" + ); + } + + // Validate that the name is in the correct format: + validateNameFormat(definition.name()); + + // Validate that there are no duplicate ids across any files: + for (var id : definition.ids()) { + if (seenIds.contains(id)) { + throw new IllegalStateException( + "Transport version definition file " + + definition.path(resourcesDir) + + " contains an id also present in another file" + ); + } + seenIds.add(id); + } + }); + } + } + +} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionReferencesTask.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionReferencesTask.java new file mode 100644 index 0000000000000..f5b8126977db4 --- /dev/null +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/transport/ValidateTransportVersionReferencesTask.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.transport; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Validates that each transport version named reference has a constant definition. + */ +@CacheableTask +public abstract class ValidateTransportVersionReferencesTask extends DefaultTask { + + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + public abstract DirectoryProperty getDefinitionsDirectory(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getReferencesFile(); + + @TaskAction + public void validateTransportVersions() throws IOException { + Path constantsDir = getDefinitionsDirectory().getAsFile().get().toPath(); + Path namesFile = getReferencesFile().get().getAsFile().toPath(); + + for (var tvReference : TransportVersionUtils.readReferencesFile(namesFile)) { + Path constantFile = constantsDir.resolve(tvReference.name() + ".csv"); + if (Files.exists(constantFile) == false) { + throw new RuntimeException( + "TransportVersion.fromName(\"" + + tvReference.name() + + "\") was used at " + + tvReference.location() + + ", but lacks a" + + " transport version constant definition. This can be generated with the task" // todo + ); + } + } + } +} diff --git a/server/build.gradle b/server/build.gradle index be2b43745d0b2..59615e5f42ac5 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -12,6 +12,8 @@ apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.internal-cluster-test' apply plugin: 'elasticsearch.internal-test-artifact' apply plugin: 'elasticsearch.test-build-info' +apply plugin: 'elasticsearch.transport-version-management' +apply plugin: 'elasticsearch.global-transport-version-management' publishing { publications { diff --git a/server/src/main/java/org/elasticsearch/TransportVersion.java b/server/src/main/java/org/elasticsearch/TransportVersion.java index 2ac4c1bf72ab6..d31bb2a5e5495 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersion.java +++ b/server/src/main/java/org/elasticsearch/TransportVersion.java @@ -399,7 +399,7 @@ private static Map loadTransportVersionsByName() { throw new UncheckedIOException("latest transport version file not found at [" + latestLocation + "]", ioe); } - String manifestLocation = "/transport/constant/manifest.txt"; + String manifestLocation = "/transport/defined/manifest.txt"; List versionFileNames = null; if (latestId > -1) { try (InputStream inputStream = TransportVersion.class.getResourceAsStream(manifestLocation)) { @@ -414,7 +414,7 @@ private static Map loadTransportVersionsByName() { if (versionFileNames != null) { for (String name : versionFileNames) { - String versionLocation = "/transport/constant/" + name; + String versionLocation = "/transport/defined/" + name; try (InputStream inputStream = TransportVersion.class.getResourceAsStream(versionLocation)) { if (inputStream == null) { throw new IllegalStateException("transport version file not found at [" + versionLocation + "]"); diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index e57cb485361b6..ecac23f685acf 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -211,7 +211,6 @@ static TransportVersion def(int id) { public static final TransportVersion ML_INFERENCE_COHERE_API_VERSION_8_19 = def(8_841_0_60); public static final TransportVersion ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19 = def(8_841_0_61); public static final TransportVersion ESQL_PROFILE_INCLUDE_PLAN_8_19 = def(8_841_0_62); - public static final TransportVersion ESQL_SPLIT_ON_BIG_VALUES_8_19 = def(8_841_0_63); public static final TransportVersion ESQL_FIXED_INDEX_LIKE_8_19 = def(8_841_0_64); public static final TransportVersion V_9_0_0 = def(9_000_0_09); public static final TransportVersion INITIAL_ELASTICSEARCH_9_0_1 = def(9_000_0_10); @@ -330,14 +329,12 @@ static TransportVersion def(int id) { public static final TransportVersion ML_INFERENCE_COHERE_API_VERSION = def(9_110_0_00); public static final TransportVersion ESQL_PROFILE_INCLUDE_PLAN = def(9_111_0_00); public static final TransportVersion MAPPINGS_IN_DATA_STREAMS = def(9_112_0_00); - public static final TransportVersion ESQL_SPLIT_ON_BIG_VALUES_9_1 = def(9_112_0_01); public static final TransportVersion ESQL_FIXED_INDEX_LIKE_9_1 = def(9_112_0_02); public static final TransportVersion ESQL_SAMPLE_OPERATOR_STATUS_9_1 = def(9_112_0_03); public static final TransportVersion INITIAL_ELASTICSEARCH_9_1_1 = def(9_112_0_04); public static final TransportVersion PROJECT_STATE_REGISTRY_RECORDS_DELETIONS = def(9_113_0_00); public static final TransportVersion ESQL_SERIALIZE_TIMESERIES_FIELD_TYPE = def(9_114_0_00); public static final TransportVersion ML_INFERENCE_IBM_WATSONX_COMPLETION_ADDED = def(9_115_0_00); - public static final TransportVersion ESQL_SPLIT_ON_BIG_VALUES = def(9_116_0_00); public static final TransportVersion ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS = def(9_117_0_00); public static final TransportVersion ML_INFERENCE_CUSTOM_SERVICE_EMBEDDING_TYPE = def(9_118_0_00); public static final TransportVersion ESQL_FIXED_INDEX_LIKE = def(9_119_0_00); diff --git a/server/src/main/resources/transport/defined/esql_split_on_big_values.csv b/server/src/main/resources/transport/defined/esql_split_on_big_values.csv new file mode 100644 index 0000000000000..3d0c94f8fa4c2 --- /dev/null +++ b/server/src/main/resources/transport/defined/esql_split_on_big_values.csv @@ -0,0 +1 @@ +9116000,9112001,8841063 diff --git a/server/src/test/resources/transport/constant/manifest.txt b/server/src/test/resources/transport/defined/manifest.txt similarity index 100% rename from server/src/test/resources/transport/constant/manifest.txt rename to server/src/test/resources/transport/defined/manifest.txt diff --git a/server/src/test/resources/transport/constant/test_0.csv b/server/src/test/resources/transport/defined/test_0.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_0.csv rename to server/src/test/resources/transport/defined/test_0.csv diff --git a/server/src/test/resources/transport/constant/test_1.csv b/server/src/test/resources/transport/defined/test_1.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_1.csv rename to server/src/test/resources/transport/defined/test_1.csv diff --git a/server/src/test/resources/transport/constant/test_2.csv b/server/src/test/resources/transport/defined/test_2.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_2.csv rename to server/src/test/resources/transport/defined/test_2.csv diff --git a/server/src/test/resources/transport/constant/test_3.csv b/server/src/test/resources/transport/defined/test_3.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_3.csv rename to server/src/test/resources/transport/defined/test_3.csv diff --git a/server/src/test/resources/transport/constant/test_4.csv b/server/src/test/resources/transport/defined/test_4.csv similarity index 100% rename from server/src/test/resources/transport/constant/test_4.csv rename to server/src/test/resources/transport/defined/test_4.csv diff --git a/x-pack/plugin/esql/compute/build.gradle b/x-pack/plugin/esql/compute/build.gradle index 66867ae668fcc..ebdf3074e9c8b 100644 --- a/x-pack/plugin/esql/compute/build.gradle +++ b/x-pack/plugin/esql/compute/build.gradle @@ -3,6 +3,7 @@ import org.elasticsearch.gradle.internal.util.SourceDirectoryCommandLineArgument apply plugin: 'elasticsearch.build' apply plugin: 'elasticsearch.string-templates' apply plugin: 'elasticsearch.publish' +apply plugin: 'elasticsearch.transport-version-management' base { archivesName = 'x-pack-esql-compute' diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/read/ValuesSourceReaderOperatorStatus.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/read/ValuesSourceReaderOperatorStatus.java index 4a8fcda81f82a..13ed5e4c84b8f 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/read/ValuesSourceReaderOperatorStatus.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/read/ValuesSourceReaderOperatorStatus.java @@ -23,9 +23,6 @@ import static org.elasticsearch.TransportVersions.ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED; import static org.elasticsearch.TransportVersions.ESQL_DOCUMENTS_FOUND_AND_VALUES_LOADED_8_19; -import static org.elasticsearch.TransportVersions.ESQL_SPLIT_ON_BIG_VALUES; -import static org.elasticsearch.TransportVersions.ESQL_SPLIT_ON_BIG_VALUES_8_19; -import static org.elasticsearch.TransportVersions.ESQL_SPLIT_ON_BIG_VALUES_9_1; public class ValuesSourceReaderOperatorStatus extends AbstractPageMappingToIteratorOperator.Status { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( @@ -33,6 +30,7 @@ public class ValuesSourceReaderOperatorStatus extends AbstractPageMappingToItera "values_source_reader", ValuesSourceReaderOperatorStatus::readFrom ); + private static final TransportVersion SPLIT_ON_BIG_VALUES = TransportVersion.fromName("esql_split_on_big_values"); private final Map readersBuilt; private final long valuesLoaded; @@ -103,9 +101,7 @@ public void writeTo(StreamOutput out) throws IOException { } private static boolean supportsSplitOnBigValues(TransportVersion version) { - return version.onOrAfter(ESQL_SPLIT_ON_BIG_VALUES) - || version.isPatchFrom(ESQL_SPLIT_ON_BIG_VALUES_9_1) - || version.isPatchFrom(ESQL_SPLIT_ON_BIG_VALUES_8_19); + return version.supports(SPLIT_ON_BIG_VALUES); } private static boolean supportsValuesLoaded(TransportVersion version) {