Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace.package]
version = "260217.0.0"
version = "260304.0.0"
authors = ["Databend Authors <opensource@datafuselabs.com>"]
license = "Apache-2.0"
publish = false
Expand Down
144 changes: 144 additions & 0 deletions crates/common/version/src/grpc_changelog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright 2021 Datafuse Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Changelog of gRPC version compatibility.
//!
//! Records the minimum compatible client and server versions at each package
//! version where compatibility changed. Consumers use
//! `.range(..=v).last()` to find the applicable entry for a given version.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs recommend using .range(..=v).last() to find the applicable entry. On a BTreeMap, last() will iterate through the entire range (O(n)); since BTreeMap::Range is a DoubleEndedIterator, using .range(..=v).next_back() returns the same entry without scanning.

Suggested change
//! `.range(..=v).last()` to find the applicable entry for a given version.
//! `.range(..=v).next_back()` to find the applicable entry for a given version.

Copilot uses AI. Check for mistakes.

use std::collections::BTreeMap;

use crate::version::Version;

/// Min-compatible client and server versions at a given package version.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrpcVersionCompat {
pub min_client: Version,
pub min_server: Version,
}

/// Returns a changelog mapping each package version (where compatibility
/// changed) to the min-compatible client and server versions at that point.
///
/// Only versions where `min_client` or `min_server` changed are included.
pub fn grpc_changelog() -> BTreeMap<Version, GrpcVersionCompat> {
const fn ver(major: u64, minor: u64, patch: u64) -> Version {
Version::new(major, minor, patch)
}

let mut m = BTreeMap::new();

// 260205.0.0: client adds ExpireInMillis, PutSequential (server since 1.2.770)
m.insert(ver(260205, 0, 0), GrpcVersionCompat {
min_client: ver(1, 2, 676),
min_server: ver(1, 2, 770),
});

// 260214.0.0: client adds KvGetMany(srv:1.2.869), TransactionPrevValue(srv:1.2.304)
m.insert(ver(260214, 0, 0), GrpcVersionCompat {
min_client: ver(1, 2, 676),
min_server: ver(1, 2, 869),
});

// 260217.0.0: client adds KvTransactionPutMatchSeq (server since 260217.0.0)
#[cfg(feature = "txn-put-match-seq")]
m.insert(ver(260217, 0, 0), GrpcVersionCompat {
min_client: ver(1, 2, 676),
min_server: ver(260217, 0, 0),
});

// 260217.0.0: without txn-put-match-seq, no new client requirement
#[cfg(not(feature = "txn-put-match-seq"))]
m.insert(ver(260217, 0, 0), GrpcVersionCompat {
min_client: ver(1, 2, 676),
min_server: ver(1, 2, 869),
});

// 260304.0.0: no compatibility change
#[cfg(feature = "txn-put-match-seq")]
m.insert(ver(260304, 0, 0), GrpcVersionCompat {
min_client: ver(1, 2, 676),
min_server: ver(260217, 0, 0),
});

#[cfg(not(feature = "txn-put-match-seq"))]
m.insert(ver(260304, 0, 0), GrpcVersionCompat {
min_client: ver(1, 2, 676),
min_server: ver(1, 2, 869),
});
Comment on lines +32 to +80
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function/docs state that only versions where min_client/min_server changed are included, but the map unconditionally adds an entry for 260304.0.0 even though the comment says "no compatibility change". This inconsistency will force adding a new entry on every version bump; consider either (a) removing no-op entries like 260304.0.0 and updating the docs/tests to use a range(..=v).next_back() lookup, or (b) changing the docs to explicitly state that the current version is always present even when compatibility did not change.

Copilot uses AI. Check for mistakes.

m
}

#[cfg(test)]
mod tests {
use super::*;
use crate::MIN_CLIENT_VERSION;
use crate::MIN_SERVER_VERSION;

#[test]
fn test_grpc_changelog_contains_current_version() {
let changelog = grpc_changelog();
let current = *crate::version();

assert!(
changelog.contains_key(&current),
"changelog must contain an entry for the current version {}",
current
);
Comment on lines +96 to +100
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_grpc_changelog_contains_current_version enforces that every release version must have an explicit changelog entry, even when compatibility didn't change (which is why 260304.0.0 had to be added). If the goal is to ensure the changelog can answer queries for the current version, a more stable assertion is to look up changelog.range(..=current).next_back() and validate it matches MIN_CLIENT_VERSION/MIN_SERVER_VERSION (and optionally that current >= *changelog.keys().next_back().unwrap()).

Suggested change
assert!(
changelog.contains_key(&current),
"changelog must contain an entry for the current version {}",
current
);
let (_, compat) = changelog
.range(..=current)
.next_back()
.unwrap_or_else(|| {
panic!(
"changelog must contain an entry for or before the current version {}",
current
)
});
assert_eq!(
compat.min_client, MIN_CLIENT_VERSION,
"changelog entry for or before current version {} must match MIN_CLIENT_VERSION",
current
);
assert_eq!(
compat.min_server, MIN_SERVER_VERSION,
"changelog entry for or before current version {} must match MIN_SERVER_VERSION",
current
);

Copilot uses AI. Check for mistakes.
}

#[test]
fn test_grpc_changelog_last_entry_matches_current() {
let changelog = grpc_changelog();
let (_, last) = changelog.iter().next_back().unwrap();

assert_eq!(
last.min_client, MIN_CLIENT_VERSION,
"last changelog min_client must match MIN_CLIENT_VERSION"
);

assert_eq!(
last.min_server, MIN_SERVER_VERSION,
"last changelog min_server must match MIN_SERVER_VERSION"
);
}

#[test]
fn test_grpc_changelog_monotonic() {
let changelog = grpc_changelog();
let mut prev_client = Version::min();
let mut prev_server = Version::min();

for (ver, compat) in &changelog {
assert!(
compat.min_client >= prev_client,
"min_client decreased at {}: {:?} < {:?}",
ver,
compat.min_client,
prev_client
);
assert!(
compat.min_server >= prev_server,
"min_server decreased at {}: {:?} < {:?}",
ver,
compat.min_server,
prev_server
);
prev_client = compat.min_client;
prev_server = compat.min_server;
}
}
}
9 changes: 6 additions & 3 deletions crates/common/version/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
//! See [Compatibility Algorithm](./compatibility_algorithm.md) for details.

mod feature_span;
mod grpc_changelog;
mod grpc_feat;
mod grpc_spec;
mod raft_feat;
Expand All @@ -41,6 +42,8 @@ mod version;
pub use self::feature_span::FeatureSet;
pub use self::feature_span::FeatureSpan;
pub use self::feature_span::FeatureSpec;
pub use self::grpc_changelog::GrpcVersionCompat;
pub use self::grpc_changelog::grpc_changelog;
pub use self::grpc_feat::GrpcFeature;
pub use self::grpc_spec::GrpcSpec;
pub use self::raft_feat::RaftFeature;
Expand Down Expand Up @@ -128,17 +131,17 @@ mod tests {

#[test]
fn test_version_string() {
assert_eq!(version_str(), "260217.0.0");
assert_eq!(version_str(), "260304.0.0");
}

#[test]
fn test_semver_components() {
assert_eq!(semver_tuple(version()), (260217, 0, 0));
assert_eq!(semver_tuple(version()), (260304, 0, 0));
}

#[test]
fn test_semver_display() {
assert_eq!(version().to_semver().to_string(), "260217.0.0");
assert_eq!(version().to_semver().to_string(), "260304.0.0");
}

#[test]
Expand Down