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"