Skip to content

Commit 29c5f97

Browse files
committed
Check versions compatibility during init
1 parent 509c5e8 commit 29c5f97

File tree

6 files changed

+168
-10
lines changed

6 files changed

+168
-10
lines changed

src/qdrant_client/collection.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,9 @@ use tonic::Status;
66

77
use crate::auth::TokenInterceptor;
88
use crate::qdrant::collections_client::CollectionsClient;
9-
use crate::qdrant::{
10-
alias_operations, AliasOperations, ChangeAliases, CollectionClusterInfoRequest,
11-
CollectionClusterInfoResponse, CollectionExistsRequest, CollectionOperationResponse,
12-
CreateAlias, CreateCollection, DeleteAlias, DeleteCollection, GetCollectionInfoRequest,
13-
GetCollectionInfoResponse, ListAliasesRequest, ListAliasesResponse,
14-
ListCollectionAliasesRequest, ListCollectionsRequest, ListCollectionsResponse, RenameAlias,
15-
UpdateCollection, UpdateCollectionClusterSetupRequest, UpdateCollectionClusterSetupResponse,
16-
};
9+
use crate::qdrant::{alias_operations, AliasOperations, ChangeAliases, CollectionClusterInfoRequest, CollectionClusterInfoResponse, CollectionExistsRequest, CollectionOperationResponse, CreateAlias, CreateCollection, DeleteAlias, DeleteCollection, GetCollectionInfoRequest, GetCollectionInfoResponse, ListAliasesRequest, ListAliasesResponse, ListCollectionAliasesRequest, ListCollectionsRequest, ListCollectionsResponse, RenameAlias, UpdateCollection, UpdateCollectionClusterSetupRequest, UpdateCollectionClusterSetupResponse};
1710
use crate::qdrant_client::{Qdrant, QdrantResult};
11+
use crate::qdrant_client::version_check::is_compatible;
1812

1913
/// # Collection operations
2014
///
@@ -27,6 +21,26 @@ impl Qdrant {
2721
&self,
2822
f: impl Fn(CollectionsClient<InterceptedService<Channel, TokenInterceptor>>) -> O,
2923
) -> QdrantResult<T> {
24+
if self.config.check_compatibility && self.is_compatible() == None {
25+
let client_version = env!("CARGO_PKG_VERSION").to_string();
26+
let server_version = match self.health_check().await {
27+
Ok(info) => info.version,
28+
Err(_) => "Unknown".to_string(),
29+
};
30+
if server_version == "Unknown" {
31+
println!("Failed to obtain server version. \
32+
Unable to check client-server compatibility. \
33+
Set check_compatibility=false to skip version check.");
34+
} else {
35+
let is_compatible = is_compatible(Some(&client_version), Some(&server_version));
36+
self.set_is_compatible(Some(is_compatible));
37+
println!("Client version {client_version} is not compatible with server version {server_version}. \
38+
Major versions should match and minor version difference must not exceed 1. \
39+
Set check_compatibility=false to skip version check.");
40+
41+
}
42+
}
43+
3044
let result = self
3145
.channel
3246
.with_channel(

src/qdrant_client/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ pub struct QdrantConfig {
3434

3535
/// Optional compression schema to use for API requests
3636
pub compression: Option<CompressionEncoding>,
37+
38+
/// Whether to check compatibility between the client and server versions
39+
pub check_compatibility: bool,
40+
3741
}
3842

3943
impl QdrantConfig {
@@ -169,6 +173,12 @@ impl QdrantConfig {
169173
pub fn build(self) -> Result<Qdrant, QdrantError> {
170174
Qdrant::new(self)
171175
}
176+
177+
pub fn skip_compatibility_check(mut self) -> Self {
178+
self.check_compatibility = false;
179+
self
180+
}
181+
172182
}
173183

174184
/// Default Qdrant client configuration.
@@ -183,6 +193,7 @@ impl Default for QdrantConfig {
183193
keep_alive_while_idle: true,
184194
api_key: None,
185195
compression: None,
196+
check_compatibility: true,
186197
}
187198
}
188199
}

src/qdrant_client/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ mod query;
1010
mod search;
1111
mod sharding_keys;
1212
mod snapshot;
13+
mod version_check;
1314

15+
use std::cell::{RefCell};
1416
use std::future::Future;
15-
1617
use tonic::codegen::InterceptedService;
1718
use tonic::transport::{Channel, Uri};
1819
use tonic::Status;
@@ -85,6 +86,9 @@ pub struct Qdrant {
8586

8687
/// Internal connection pool
8788
channel: ChannelPool,
89+
90+
/// Internal flag for checking compatibility with the server
91+
is_compatible: RefCell<Option<bool>>,
8892
}
8993

9094
/// # Construct and connect
@@ -102,11 +106,19 @@ impl Qdrant {
102106
config.keep_alive_while_idle,
103107
);
104108

105-
let client = Self { channel, config };
109+
let client = Self { channel, config, is_compatible: RefCell::new(None) };
106110

107111
Ok(client)
108112
}
109113

114+
fn set_is_compatible(&self, value: Option<bool>) {
115+
*self.is_compatible.borrow_mut() = value;
116+
}
117+
118+
fn is_compatible(&self) -> Option<bool> {
119+
*self.is_compatible.borrow()
120+
}
121+
110122
/// Build a new Qdrant client with the given URL.
111123
///
112124
/// ```no_run

src/qdrant_client/points.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::qdrant::{
1313
UpdateBatchResponse, UpdatePointVectors, UpsertPoints,
1414
};
1515
use crate::qdrant_client::{Qdrant, QdrantResult};
16+
use crate::qdrant_client::version_check::is_compatible;
1617

1718
/// # Point operations
1819
///
@@ -24,6 +25,26 @@ impl Qdrant {
2425
&self,
2526
f: impl Fn(PointsClient<InterceptedService<Channel, TokenInterceptor>>) -> O,
2627
) -> QdrantResult<T> {
28+
if self.config.check_compatibility && self.is_compatible() == None {
29+
let client_version = env!("CARGO_PKG_VERSION").to_string();
30+
let server_version = match self.health_check().await {
31+
Ok(info) => info.version,
32+
Err(_) => "Unknown".to_string(),
33+
};
34+
if server_version == "Unknown" {
35+
println!("Failed to obtain server version. \
36+
Unable to check client-server compatibility. \
37+
Set check_compatibility=false to skip version check.");
38+
} else {
39+
let is_compatible = is_compatible(Some(&client_version), Some(&server_version));
40+
self.set_is_compatible(Some(is_compatible));
41+
println!("Client version {client_version} is not compatible with server version {server_version}. \
42+
Major versions should match and minor version difference must not exceed 1. \
43+
Set check_compatibility=false to skip version check.");
44+
45+
}
46+
}
47+
2748
let result = self
2849
.channel
2950
.with_channel(

src/qdrant_client/snapshot.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::qdrant::{
1212
ListFullSnapshotsRequest, ListSnapshotsRequest, ListSnapshotsResponse,
1313
};
1414
use crate::qdrant_client::{Qdrant, QdrantResult};
15+
use crate::qdrant_client::version_check::is_compatible;
1516

1617
/// # Snapshot operations
1718
///
@@ -23,6 +24,26 @@ impl Qdrant {
2324
&self,
2425
f: impl Fn(SnapshotsClient<InterceptedService<Channel, TokenInterceptor>>) -> O,
2526
) -> QdrantResult<T> {
27+
if self.config.check_compatibility && self.is_compatible() == None {
28+
let client_version = env!("CARGO_PKG_VERSION").to_string();
29+
let server_version = match self.health_check().await {
30+
Ok(info) => info.version,
31+
Err(_) => "Unknown".to_string(),
32+
};
33+
if server_version == "Unknown" {
34+
println!("Failed to obtain server version. \
35+
Unable to check client-server compatibility. \
36+
Set check_compatibility=false to skip version check.");
37+
} else {
38+
let is_compatible = is_compatible(Some(&client_version), Some(&server_version));
39+
self.set_is_compatible(Some(is_compatible));
40+
println!("Client version {client_version} is not compatible with server version {server_version}. \
41+
Major versions should match and minor version difference must not exceed 1. \
42+
Set check_compatibility=false to skip version check.");
43+
44+
}
45+
}
46+
2647
let result = self
2748
.channel
2849
.with_channel(

src/qdrant_client/version_check.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::error::Error;
2+
use std::fmt;
3+
4+
#[derive(Debug, Clone)]
5+
pub struct Version {
6+
pub major: u32,
7+
pub minor: u32
8+
}
9+
10+
impl Version {
11+
pub fn parse(version: &str) -> Result<Version, VersionParseError> {
12+
if version.is_empty() {
13+
return Err(VersionParseError::EmptyVersion);
14+
}
15+
let parts: Vec<&str> = version.split('.').collect();
16+
if parts.len() < 2 {
17+
return Err(VersionParseError::InvalidFormat(version.to_string()));
18+
}
19+
20+
let major = parts[0]
21+
.parse::<u32>()
22+
.map_err(|_| VersionParseError::InvalidFormat(version.to_string()))?;
23+
let minor = parts[1]
24+
.parse::<u32>()
25+
.map_err(|_| VersionParseError::InvalidFormat(version.to_string()))?;
26+
27+
Ok(Version { major, minor })
28+
}
29+
}
30+
31+
#[derive(Debug)]
32+
pub enum VersionParseError {
33+
EmptyVersion,
34+
InvalidFormat(String),
35+
}
36+
37+
impl fmt::Display for VersionParseError {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
match self {
40+
VersionParseError::EmptyVersion => write!(f, "Version is empty"),
41+
VersionParseError::InvalidFormat(version) => {
42+
write!(f, "Unable to parse version, expected format: x.y[.z], found: {}", version)
43+
}
44+
}
45+
}
46+
}
47+
48+
impl Error for VersionParseError {}
49+
50+
pub fn is_compatible(client_version: Option<&str>, server_version: Option<&str>) -> bool {
51+
if client_version.is_none() || server_version.is_none() {
52+
println!(
53+
"Unable to compare versions, client_version: {:?}, server_version: {:?}",
54+
client_version, server_version
55+
);
56+
return false;
57+
}
58+
59+
let client_version = client_version.unwrap();
60+
let server_version = server_version.unwrap();
61+
62+
if client_version == server_version {
63+
return true;
64+
}
65+
66+
match (Version::parse(client_version), Version::parse(server_version)) {
67+
(Ok(client), Ok(server)) => {
68+
let major_dif = (client.major as i32 - server.major as i32).abs();
69+
if major_dif >= 1 {
70+
return false;
71+
}
72+
(client.minor as i32 - server.minor as i32).abs() <= 1
73+
}
74+
(Err(e), _) | (_, Err(e)) => {
75+
println!("Unable to compare versions: {}", e);
76+
false
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)