diff --git a/.asf.yaml b/.asf.yaml index d492498..a64c7fb 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -44,7 +44,7 @@ github: branches: includes: - "~DEFAULT_BRANCH" - - "release/*" + - "release-*" excludes: [] bypass_teams: - root diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edeae96..ad678c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,9 @@ jobs: tools/convert_ann_benchmarks.py \ tools/verify_binary_artifact.py + - name: Test release helper scripts + run: python3 -m unittest discover -s tools/tests -p 'test_*.py' -v + - name: Validate .asf.yaml run: pip install pyyaml -q && python3 tools/validate_asf_yaml.py diff --git a/DEPENDENCIES.rust.tsv b/DEPENDENCIES.rust.tsv index 191f681..9a04218 100644 --- a/DEPENDENCIES.rust.tsv +++ b/DEPENDENCIES.rust.tsv @@ -51,9 +51,9 @@ num-rational@0.4.2 X X num-traits@0.2.19 X X once_cell@1.21.4 X X once_cell_polyfill@1.70.2 X X -paimon-vindex-core@0.3.0 X -paimon-vindex-ffi@0.3.0 X -paimon-vindex-jni@0.3.0 X +paimon-vindex-core@0.4.0 X +paimon-vindex-ffi@0.4.0 X +paimon-vindex-jni@0.4.0 X paste@1.0.15 X X ppv-lite86@0.2.21 X X proc-macro2@1.0.106 X X diff --git a/core/DEPENDENCIES.rust.tsv b/core/DEPENDENCIES.rust.tsv index 5b0a16f..2c37814 100644 --- a/core/DEPENDENCIES.rust.tsv +++ b/core/DEPENDENCIES.rust.tsv @@ -19,7 +19,7 @@ num-complex@0.4.6 X X num-integer@0.1.46 X X num-rational@0.4.2 X X num-traits@0.2.19 X X -paimon-vindex-core@0.3.0 X +paimon-vindex-core@0.4.0 X paste@1.0.15 X X ppv-lite86@0.2.21 X X proc-macro2@1.0.106 X X diff --git a/docs/releases.html b/docs/releases.html index aed9f95..46f2d32 100644 --- a/docs/releases.html +++ b/docs/releases.html @@ -31,7 +31,7 @@

Releases

- +

Distribution channels

@@ -47,9 +47,15 @@

Distribution channels

-

Upcoming: 0.3.0

-

The repository is currently developing the 0.3.0 line. Until an ASF vote passes and the signed source archive appears under Apache downloads, code and packages from this line are development artifacts rather than an Apache release.

+

Upcoming: 0.4.0

+

The repository is currently developing the 0.4.0 line. The planned release improves filtered and batched IVF scans, stabilizes IVF-PQ training, adds extensible search parameters across language bindings, bridges native diagnostics to Java logging, and tightens serialization allocation bounds. Until an ASF vote passes and the signed source archive appears under Apache downloads, code and packages from this line are development artifacts rather than an Apache release.

+
+ +
+

0.3.0

+

Released 30 July 2026. This release added native DiskANN and adaptive IVF indexes, IVF-RQ query-width controls and search optimizations, batched positional reads across C and Python, expanded architecture and API documentation, and reproducible source and multi-platform convenience-artifact release verification.

Stored-index upgrade noteVersion 0.3.0 intentionally does not read the experimental IVF-HNSW-FLAT (IHFL) and IVF-HNSW-SQ (IHSQ) files written by 0.2.x. Rebuild those indexes with a 0.3.0-supported index type during upgrade. Index files written by 0.3.0 are likewise not a safe rollback boundary for 0.2.x; retain source vectors or a 0.2-compatible index copy until the upgrade is accepted.
+

Source release · Release notes · Rust crate · Java artifacts · Python package

diff --git a/ffi/DEPENDENCIES.rust.tsv b/ffi/DEPENDENCIES.rust.tsv index ee61049..d136320 100644 --- a/ffi/DEPENDENCIES.rust.tsv +++ b/ffi/DEPENDENCIES.rust.tsv @@ -44,8 +44,8 @@ num-rational@0.4.2 X X num-traits@0.2.19 X X once_cell@1.21.4 X X once_cell_polyfill@1.70.2 X X -paimon-vindex-core@0.3.0 X -paimon-vindex-ffi@0.3.0 X +paimon-vindex-core@0.4.0 X +paimon-vindex-ffi@0.4.0 X paste@1.0.15 X X ppv-lite86@0.2.21 X X proc-macro2@1.0.106 X X diff --git a/jni/DEPENDENCIES.rust.tsv b/jni/DEPENDENCIES.rust.tsv index 8a6e73a..e069be3 100644 --- a/jni/DEPENDENCIES.rust.tsv +++ b/jni/DEPENDENCIES.rust.tsv @@ -28,8 +28,8 @@ num-complex@0.4.6 X X num-integer@0.1.46 X X num-rational@0.4.2 X X num-traits@0.2.19 X X -paimon-vindex-core@0.3.0 X -paimon-vindex-jni@0.3.0 X +paimon-vindex-core@0.4.0 X +paimon-vindex-jni@0.4.0 X paste@1.0.15 X X ppv-lite86@0.2.21 X X proc-macro2@1.0.106 X X diff --git a/tools/create_release_branch.sh b/tools/create_release_branch.sh index 71fd87c..c2b44f8 100755 --- a/tools/create_release_branch.sh +++ b/tools/create_release_branch.sh @@ -26,6 +26,7 @@ MVN=${MVN:-mvn} # fail immediately set -o errexit set -o nounset +set -o pipefail # print command before executing set -o xtrace @@ -37,19 +38,20 @@ fi ########################### -if [ -z "${RELEASE_VERSION}" ]; then +if [ -z "${RELEASE_VERSION:-}" ]; then echo "RELEASE_VERSION is unset" exit 1 fi cd .. -target_branch=release-${RELEASE_VERSION} +SHORT_RELEASE_VERSION=${RELEASE_VERSION%.*} +target_branch=release-${SHORT_RELEASE_VERSION} if [ "${RELEASE_CANDIDATE}" != "none" ]; then target_branch=${target_branch}-rc${RELEASE_CANDIDATE} fi -git checkout -b ${target_branch} +git checkout -b "${target_branch}" RELEASE_HASH=`git rev-parse HEAD` echo "Echo created release hash $RELEASE_HASH" diff --git a/tools/tests/test_release_scripts.py b/tools/tests/test_release_scripts.py new file mode 100644 index 0000000..aa650ca --- /dev/null +++ b/tools/tests/test_release_scripts.py @@ -0,0 +1,252 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +class ReleaseScriptTest(unittest.TestCase): + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.repo = Path(self.temp_dir.name) + (self.repo / "tools").mkdir() + (self.repo / "core").mkdir() + (self.repo / "ffi").mkdir() + (self.repo / "jni").mkdir() + (self.repo / "python").mkdir() + (self.repo / "java").mkdir() + + shutil.copy( + REPO_ROOT / "tools" / "update_branch_version.sh", + self.repo / "tools" / "update_branch_version.sh", + ) + shutil.copy( + REPO_ROOT / "tools" / "create_release_branch.sh", + self.repo / "tools" / "create_release_branch.sh", + ) + + self._write( + "Cargo.toml", + """ +[workspace] +members = ["core", "ffi", "jni"] +resolver = "2" +""", + ) + self._write(".gitignore", "/target/\n") + self._write( + "core/Cargo.toml", + """ +[package] +name = "paimon-vindex-core" +version = "0.4.0" +edition = "2021" +""", + ) + self._write("core/src/lib.rs", "") + self._write( + "ffi/Cargo.toml", + """ +[package] +name = "paimon-vindex-ffi" +version = "0.4.0" +edition = "2021" + +[dependencies] +paimon-vindex-core = { path = "../core", version = "0.4.0" } +""", + ) + self._write("ffi/src/lib.rs", "") + self._write( + "jni/Cargo.toml", + """ +[package] +name = "paimon-vindex-jni" +version = "0.4.0" +edition = "2021" + +[dependencies] +paimon-vindex-core = { path = "../core", version = "0.4.0" } +""", + ) + self._write("jni/src/lib.rs", "") + self._write( + "python/pyproject.toml", + """ +[project] +name = "paimon-vindex" +version = "0.4.0" +""", + ) + self._write( + "java/pom.xml", + """ + + 4.0.0 + org.apache.paimon + paimon-vector-index-java + 0.4.0-SNAPSHOT + +""", + ) + + self._run(["git", "init", "-q"]) + self._run(["git", "config", "user.name", "Release Script Test"]) + self._run(["git", "config", "user.email", "release-script@example.com"]) + self._run(["cargo", "generate-lockfile"]) + self._run(["git", "add", "."]) + self._run(["git", "commit", "-q", "-m", "initial"]) + + def tearDown(self): + self.temp_dir.cleanup() + + def test_update_branch_version_keeps_workspace_versions_consistent(self): + env = os.environ.copy() + env.update( + { + "OLD_VERSION": "0.4.0", + "NEW_VERSION": "0.5.0-SNAPSHOT", + } + ) + self._run( + ["bash", "update_branch_version.sh"], + cwd=self.repo / "tools", + env=env, + ) + + self.assertFalse( + (self.repo / "target").exists(), + "version updates should not compile the workspace", + ) + self.assertIn( + 'version = "0.5.0"', + (self.repo / "core" / "Cargo.toml").read_text(), + ) + for crate in ("ffi", "jni"): + manifest = (self.repo / crate / "Cargo.toml").read_text() + self.assertIn('version = "0.5.0"', manifest) + self.assertIn( + 'paimon-vindex-core = { path = "../core", version = "0.5.0" }', + manifest, + ) + self.assertIn( + 'version = "0.5.0"', + (self.repo / "python" / "pyproject.toml").read_text(), + ) + self.assertIn( + "0.5.0-SNAPSHOT", + (self.repo / "java" / "pom.xml").read_text(), + ) + + lockfile = (self.repo / "Cargo.lock").read_text() + for package in ( + "paimon-vindex-core", + "paimon-vindex-ffi", + "paimon-vindex-jni", + ): + self.assertIn( + f'name = "{package}"\nversion = "0.5.0"', + lockfile, + ) + + subject = self._run( + ["git", "log", "-1", "--format=%s"], + capture_output=True, + ).stdout.strip() + self.assertEqual("Update version to 0.5.0-SNAPSHOT", subject) + self.assertEqual( + "", + self._run( + ["git", "status", "--short"], + capture_output=True, + ).stdout.strip(), + ) + + def test_create_release_branch_uses_minor_release_line(self): + env = os.environ.copy() + env["RELEASE_VERSION"] = "0.4.0" + self._run( + ["bash", "create_release_branch.sh"], + cwd=self.repo / "tools", + env=env, + ) + + branch = self._run( + ["git", "branch", "--show-current"], + capture_output=True, + ).stdout.strip() + self.assertEqual("release-0.4", branch) + + def test_update_branch_version_reports_missing_old_version(self): + env = os.environ.copy() + env.pop("OLD_VERSION", None) + env["NEW_VERSION"] = "0.5.0-SNAPSHOT" + result = self._run( + ["bash", "update_branch_version.sh"], + cwd=self.repo / "tools", + env=env, + check=False, + capture_output=True, + ) + + self.assertNotEqual(0, result.returncode) + self.assertIn( + "OLD_VERSION is unset", + result.stdout + result.stderr, + ) + + def test_create_release_branch_reports_missing_release_version(self): + env = os.environ.copy() + env.pop("RELEASE_VERSION", None) + result = self._run( + ["bash", "create_release_branch.sh"], + cwd=self.repo / "tools", + env=env, + check=False, + capture_output=True, + ) + + self.assertNotEqual(0, result.returncode) + self.assertIn( + "RELEASE_VERSION is unset", + result.stdout + result.stderr, + ) + + def _write(self, relative_path, content): + path = self.repo / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content.lstrip()) + + def _run(self, command, cwd=None, env=None, check=True, capture_output=False): + return subprocess.run( + command, + cwd=cwd or self.repo, + env=env, + check=check, + text=True, + capture_output=capture_output, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/update_branch_version.sh b/tools/update_branch_version.sh index 03ec8a8..9999ad0 100755 --- a/tools/update_branch_version.sh +++ b/tools/update_branch_version.sh @@ -25,6 +25,7 @@ MVN=${MVN:-mvn} # fail immediately set -o errexit set -o nounset +set -o pipefail # print command before executing set -o xtrace @@ -36,33 +37,38 @@ fi ########################### -OLD_VERSION=${OLD_VERSION} -NEW_VERSION=${NEW_VERSION} - - -if [ -z "${OLD_VERSION}" ]; then +if [ -z "${OLD_VERSION:-}" ]; then echo "OLD_VERSION is unset" exit 1 fi -if [ -z "${NEW_VERSION}" ]; then +if [ -z "${NEW_VERSION:-}" ]; then echo "NEW_VERSION is unset" exit 1 fi cd .. -# For Cargo.toml and pyproject.toml, strip any -SNAPSHOT suffix (not valid in those ecosystems) +# Cargo and Python use clean SemVer versions without Maven's -SNAPSHOT suffix. +OLD_VERSION_CLEAN=$(echo "$OLD_VERSION" | sed 's/-SNAPSHOT//') NEW_VERSION_CLEAN=$(echo "$NEW_VERSION" | sed 's/-SNAPSHOT//') -#change version in all pom files (match both exact and -SNAPSHOT suffix) +# Change version in all pom files (match both exact and -SNAPSHOT suffix). find . -name 'pom.xml' -type f -exec perl -pi -e 's#'$OLD_VERSION'(-SNAPSHOT)?#'$NEW_VERSION'#' {} \; -#change version in Cargo.toml files -find . -name 'Cargo.toml' -not -path '*/target/*' -type f -exec perl -pi -e 's#^version = "'$OLD_VERSION'"#version = "'$NEW_VERSION_CLEAN'"#' {} \; +# Change workspace package versions and version requirements for local +# paimon-vindex path dependencies without touching unrelated dependencies. +find . -name 'Cargo.toml' -not -path '*/target/*' -type f \ + -exec perl -pi -e 's#^version = "'$OLD_VERSION_CLEAN'"#version = "'$NEW_VERSION_CLEAN'"#' {} \; +find . -name 'Cargo.toml' -not -path '*/target/*' -type f \ + -exec perl -pi -e 'if (/^paimon-vindex-/) { s#version = "'$OLD_VERSION_CLEAN'"#version = "'$NEW_VERSION_CLEAN'"# }' {} \; + +# Change the Python package version. +perl -pi -e 's#^version = "'$OLD_VERSION_CLEAN'"#version = "'$NEW_VERSION_CLEAN'"#' python/pyproject.toml -#change version in pyproject.toml -perl -pi -e 's#^version = "'$OLD_VERSION'"#version = "'$NEW_VERSION_CLEAN'"#' python/pyproject.toml +# Refresh workspace package versions in Cargo.lock while preserving the +# already locked third-party dependency set without compiling the workspace. +cargo update --workspace git commit -am "Update version to $NEW_VERSION"