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
29 changes: 19 additions & 10 deletions src/interpreter/clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,29 @@ impl ClickHouse {
.with_setting("low_cardinality_allow_in_native_format", false, true);
let pool = Pool::new(connect_options);

let version = pool
.get_handle()
.await
.map_err(|e| {
Error::msg(format!(
"Cannot connect to ClickHouse at {} ({})",
options.url_safe, e
))
})?
let mut handle = pool.get_handle().await.map_err(|e| {
Error::msg(format!(
"Cannot connect to ClickHouse at {} ({})",
options.url_safe, e
))
})?;

let version = handle
.query("SELECT version()")
.fetch_all()
.await?
.get::<String, _>(0, 0)?;
let quirks = ClickHouseQuirks::new(version.clone());

// Get VERSION_DESCRIBE from system.build_options for full version info (only build_options
// include version prefix, i.e. -stable/-testing)
let version = handle
.query("SELECT value FROM system.build_options WHERE name = 'VERSION_DESCRIBE'")
.fetch_all()
.await?
.get::<String, _>(0, 0)
.unwrap_or_else(|_| version.clone());

let quirks = ClickHouseQuirks::new(version);
return Ok(ClickHouse {
quirks,
options,
Expand Down
109 changes: 102 additions & 7 deletions src/interpreter/clickhouse_quirks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,76 @@ pub struct ClickHouseQuirks {
mask: u64,
}

// Custom matcher, that will properly handle prerelease.
// https://github.com/dtolnay/semver/issues/323#issuecomment-2432169904
fn version_matches(version: &semver::Version, req: &semver::VersionReq) -> bool {
if req.matches(version) {
return true;
}

// This custom matching logic is needed, because semver cannot compare different version with pre-release tags
let mut version_without_pre = version.clone();
version_without_pre.pre = "".parse().unwrap();
for comp in &req.comparators {
if comp.matches(version) {
continue;
}

// If major & minor & patch are the same (or omitted),
// this means there is a mismatch on the pre-release tag
if comp.major == version.major
&& comp.minor.is_none_or(|m| m == version.minor)
&& comp.patch.is_none_or(|p| p == version.patch)
{
return false;
}

// Otherwise, compare without pre-release tags
let mut comp_without_pre = comp.clone();
comp_without_pre.pre = "".parse().unwrap();
if !comp_without_pre.matches(&version_without_pre) {
return false;
}
}
true
}

impl ClickHouseQuirks {
pub fn new(version_string: String) -> Self {
// Version::parse() supports only x.y.z and nothing more.
let ver_maj_min_patch = version_string.split('.').collect::<Vec<&str>>()[0..3].join(".");
log::debug!("Version (maj.min.patch): {}", ver_maj_min_patch);
// Version::parse() supports only x.y.z and nothing more, but we don't need anything more,
// only .minor may include new features.
let components = version_string
.strip_prefix('v')
.unwrap_or(&version_string)
.split('.')
.collect::<Vec<&str>>();
let mut ver_maj_min_patch_pre = components[0..3].join(".");
let version_pre = components.last().unwrap_or(&"-testing");
if !version_pre.ends_with("-stable") {
log::warn!(
"Non-stable version detected ({}), treating as older/development version",
version_string
);
ver_maj_min_patch_pre.push_str(&format!(
"-{}",
version_pre
.split('-')
.collect::<Vec<&str>>()
.last()
.unwrap_or(&"alpha")
));
}
log::debug!("Version (maj.min.patch.pre): {}", ver_maj_min_patch_pre);

let version = Version::parse(ver_maj_min_patch.as_str())
.unwrap_or_else(|_| panic!("Cannot parse version: {}", ver_maj_min_patch));
let mut mask: u64 = 0;
let version = Version::parse(ver_maj_min_patch_pre.as_str())
.unwrap_or_else(|_| panic!("Cannot parse version: {}", ver_maj_min_patch_pre));
log::debug!("Version: {}", version);

let mut mask: u64 = 0;
for quirk in &QUIRKS {
let version_requirement = VersionReq::parse(quirk.0)
.unwrap_or_else(|_| panic!("Cannot parse version requirements for {:?}", quirk.1));
if version_requirement.matches(&version) {
if version_matches(&version, &version_requirement) {
mask |= quirk.1 as u64;
log::warn!("Apply quirk {:?}", quirk.1);
}
Expand All @@ -81,3 +137,42 @@ impl ClickHouseQuirks {
return (self.mask & quirk as u64) != 0;
}
}

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

#[test]
fn test_stable_version() {
let quirks = ClickHouseQuirks::new("25.11.1.1-stable".to_string());
assert_eq!(quirks.get_version(), "25.11.1.1-stable");
assert!(quirks.has(ClickHouseAvailableQuirks::SystemReplicasUUID));
assert!(quirks.has(ClickHouseAvailableQuirks::ProcessesPeakThreadsUsage));
assert!(quirks.has(ClickHouseAvailableQuirks::TraceLogHasSymbols));
}

#[test]
fn test_testing_version() {
let quirks = ClickHouseQuirks::new("25.11.1.1-testing".to_string());
assert_eq!(quirks.get_version(), "25.11.1.1-testing");
assert!(!quirks.has(ClickHouseAvailableQuirks::SystemReplicasUUID));
assert!(!quirks.has(ClickHouseAvailableQuirks::ProcessesPeakThreadsUsage));
}

#[test]
fn test_next_testing_prerelease_version() {
let quirks = ClickHouseQuirks::new("25.12.1.1-testing".to_string());
assert_eq!(quirks.get_version(), "25.12.1.1-testing");
assert!(quirks.has(ClickHouseAvailableQuirks::SystemReplicasUUID));
assert!(quirks.has(ClickHouseAvailableQuirks::ProcessesPeakThreadsUsage));
}

#[test]
fn test_version_with_v_prefix() {
let quirks = ClickHouseQuirks::new("v25.11.1.1-stable".to_string());
assert_eq!(quirks.get_version(), "v25.11.1.1-stable");
assert!(quirks.has(ClickHouseAvailableQuirks::SystemReplicasUUID));
}

// Here are the tests only for version_matches(), in other aspects we are relying on semver tests
}
Loading