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
128 changes: 118 additions & 10 deletions crates/core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ use spacetimedb_lib::ConnectionId;
use spacetimedb_paths::cli::{ConfigDir, PrivKeyPath, PubKeyPath};
use spacetimedb_paths::server::{ConfigToml, MetadataTomlPath};

pub fn current_version() -> semver::Version {
env!("CARGO_PKG_VERSION").parse().unwrap()
}

/// Parse a TOML file at the given path, returning `None` if the file does not exist.
///
/// **WARNING**: Comments and formatting in the file will be lost.
Expand All @@ -23,7 +19,7 @@ pub fn parse_config<T: serde::de::DeserializeOwned>(path: &Path) -> anyhow::Resu
}
}

#[derive(serde::Serialize, serde::Deserialize)]
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct MetadataFile {
pub version: semver::Version,
pub edition: String,
Expand All @@ -35,6 +31,24 @@ pub struct MetadataFile {
}

impl MetadataFile {
pub fn new(edition: &str) -> Self {
let mut current_version: semver::Version = env!("CARGO_PKG_VERSION").parse().unwrap();
// set the patch version of newly-created metadata files to 0 -- v1.0.0
// set `cmp.patch = Some(file_version.patch)` when checking version
// compatibility, meaning it won't be forwards-compatible with a
// database claiming to be created on v1.0.1, even though that should
// work. This can be changed once we release v1.1.0, since we don't
// care about its DBs being backwards-compatible with v1.0.0 anyway.
if let semver::Version { major: 1, minor: 0, .. } = current_version {
current_version.patch = 0;
}
Self {
version: current_version,
edition: edition.to_owned(),
client_connection_id: None,
}
}

pub fn read(path: &MetadataTomlPath) -> anyhow::Result<Option<Self>> {
parse_config(path.as_ref())
}
Expand All @@ -43,15 +57,41 @@ impl MetadataFile {
path.write(self.to_string())
}

pub fn version_compatible_with(&self, version: &semver::Version) -> bool {
semver::Comparator {
/// Check if this meta file is compatible with the default meta
/// file of a just-started database, and if so return the metadata
/// to write back to the file.
///
/// `self` is the metadata file read from a database, and current is
/// the default metadata file that the active database version would
/// right to a new database.
pub fn check_compatibility_and_update(mut self, current: Self) -> anyhow::Result<Self> {
anyhow::ensure!(
self.edition == current.edition,
"metadata.toml indicates that this database is from a different \
edition of SpacetimeDB (running {:?}, but this database is {:?})",
current.edition,
self.edition,
);
let cmp = semver::Comparator {
op: semver::Op::Caret,
major: self.version.major,
minor: Some(self.version.minor),
patch: Some(self.version.patch),
patch: None,
pre: self.version.pre.clone(),
}
.matches(version)
};
anyhow::ensure!(
cmp.matches(&current.version),
"metadata.toml indicates that this database is from a newer, \
incompatible version of SpacetimeDB (running {:?}, but this \
database is from {:?})",
current.version,
self.version,
);
// bump the version in the file only if it's being run in a newer
// database -- this won't do anything until we release v1.1.0, since we
// set current.version.patch to 0 in Self::new() due to a bug in v1.0.0
self.version = std::cmp::max(self.version, current.version);
Ok(self)
}
}

Expand Down Expand Up @@ -154,3 +194,71 @@ pub fn set_table_opt_value(table: &mut toml_edit::Table, key: &str, value: Optio
table.remove(key);
}
}

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

fn mkver(major: u64, minor: u64, patch: u64) -> semver::Version {
semver::Version::new(major, minor, patch)
}

fn mkmeta(major: u64, minor: u64, patch: u64) -> MetadataFile {
MetadataFile {
version: mkver(major, minor, patch),
edition: "standalone".to_owned(),
client_connection_id: None,
}
}

#[test]
fn check_metadata_compatibility_checking() {
assert_eq!(
mkmeta(1, 0, 0)
.check_compatibility_and_update(mkmeta(1, 0, 1))
.unwrap()
.version,
mkver(1, 0, 1)
);
assert_eq!(
mkmeta(1, 0, 1)
.check_compatibility_and_update(mkmeta(1, 0, 0))
.unwrap()
.version,
mkver(1, 0, 1)
);

mkmeta(1, 1, 0)
.check_compatibility_and_update(mkmeta(1, 0, 5))
.unwrap_err();
mkmeta(2, 0, 0)
.check_compatibility_and_update(mkmeta(1, 3, 5))
.unwrap_err();
}

// this will start failing once we bump to v1.1.0 - that's fine, it can just be removed.
#[test]
fn check_v1_0_0_compatibility() {
// this is the function v1.0.0 uses to check compatibility
let version_compatible_with = |this: &MetadataFile, version: &semver::Version| {
semver::Comparator {
op: semver::Op::Caret,
major: this.version.major,
minor: Some(this.version.minor),
patch: Some(this.version.minor),
pre: Default::default(),
}
.matches(version)
};
let default_v1_0_0_meta_file = MetadataFile {
version: mkver(1, 0, 0),
edition: "standalone".to_owned(),
client_connection_id: None,
};
let meta_file_from_v1_0_x = MetadataFile::new("standalone");
assert!(version_compatible_with(
&meta_file_from_v1_0_x,
&default_v1_0_0_meta_file.version,
));
}
}
12 changes: 2 additions & 10 deletions crates/standalone/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,9 @@ impl StandaloneEnv {
) -> anyhow::Result<Arc<Self>> {
let _pid_file = data_dir.pid_file()?;
let meta_path = data_dir.metadata_toml();
let meta = MetadataFile {
version: spacetimedb::config::current_version(),
edition: "standalone".to_owned(),
client_connection_id: None,
};
let mut meta = MetadataFile::new("standalone");
if let Some(existing_meta) = MetadataFile::read(&meta_path).context("failed reading metadata.toml")? {
anyhow::ensure!(
existing_meta.version_compatible_with(&meta.version) && existing_meta.edition == meta.edition,
"metadata.toml indicates that this database is from an incompatible \
version of SpacetimeDB. please run a migration before proceeding."
);
meta = existing_meta.check_compatibility_and_update(meta)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

ah I realize now post-merge that we call this on existing_meta with meta as a param, rather than the other way around. This means we're preserving the client_connection_id from existing_meta that we weren't preserving before. Any chance there are negative side-effects of that?

}
meta.write(&meta_path).context("failed writing metadata.toml")?;

Expand Down
Loading