mirror of
https://github.com/rust-lang/cargo
synced 2024-10-01 05:23:56 +00:00
Add a module with sqlite utilities.
This adds a module as a home for general sqlite support. Initially this contains a very simple schema migration support system.
This commit is contained in:
parent
9588fb6dda
commit
34fdf5fb03
68
Cargo.lock
generated
68
Cargo.lock
generated
|
@ -8,6 +8,17 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
|
@ -17,6 +28,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
|
@ -270,6 +287,7 @@ dependencies = [
|
|||
"pathdiff",
|
||||
"pulldown-cmark",
|
||||
"rand",
|
||||
"rusqlite",
|
||||
"rustfix",
|
||||
"same-file",
|
||||
"semver",
|
||||
|
@ -886,6 +904,18 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "faster-hex"
|
||||
version = "0.8.1"
|
||||
|
@ -1806,6 +1836,19 @@ name = "hashbrown"
|
|||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
|
@ -2060,6 +2103,17 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.3.0"
|
||||
|
@ -2799,6 +2853,20 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
|
|
|
@ -73,6 +73,7 @@ pretty_assertions = "1.4.0"
|
|||
proptest = "1.3.1"
|
||||
pulldown-cmark = { version = "0.9.3", default-features = false }
|
||||
rand = "0.8.5"
|
||||
rusqlite = { version = "0.29.0", features = ["bundled"] }
|
||||
rustfix = "0.6.1"
|
||||
same-file = "1.0.6"
|
||||
security-framework = "2.9.2"
|
||||
|
@ -162,6 +163,7 @@ pasetors.workspace = true
|
|||
pathdiff.workspace = true
|
||||
pulldown-cmark.workspace = true
|
||||
rand.workspace = true
|
||||
rusqlite.workspace = true
|
||||
rustfix.workspace = true
|
||||
semver.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
|
|
@ -62,6 +62,7 @@ mod queue;
|
|||
pub mod restricted_names;
|
||||
pub mod rustc;
|
||||
mod semver_ext;
|
||||
pub mod sqlite;
|
||||
pub mod style;
|
||||
pub mod toml;
|
||||
pub mod toml_mut;
|
||||
|
|
118
src/cargo/util/sqlite.rs
Normal file
118
src/cargo/util/sqlite.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
//! Utilities to help with working with sqlite.
|
||||
|
||||
use crate::util::interning::InternedString;
|
||||
use crate::CargoResult;
|
||||
use rusqlite::types::{FromSql, FromSqlError, ToSql, ToSqlOutput};
|
||||
use rusqlite::{Connection, TransactionBehavior};
|
||||
|
||||
impl FromSql for InternedString {
|
||||
fn column_result(value: rusqlite::types::ValueRef<'_>) -> Result<Self, FromSqlError> {
|
||||
value.as_str().map(InternedString::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for InternedString {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
|
||||
Ok(ToSqlOutput::from(self.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A function or closure representing a database migration.
|
||||
///
|
||||
/// Migrations support evolving the schema and contents of the database across
|
||||
/// new versions of cargo. The [`migrate`] function should be called
|
||||
/// immediately after opening a connection to a database in order to configure
|
||||
/// the schema. Whether or not a migration has been done is tracked by the
|
||||
/// `pragma_user_version` value in the database. Typically you include the
|
||||
/// initial `CREATE TABLE` statements in the initial list, but as time goes on
|
||||
/// you can add new tables or `ALTER TABLE` statements. The migration code
|
||||
/// will only execute statements that haven't previously been run.
|
||||
///
|
||||
/// Important things to note about how you define migrations:
|
||||
///
|
||||
/// * Never remove a migration entry from the list. Migrations are tracked by
|
||||
/// the index number in the list.
|
||||
/// * Never perform any schema modifications that would be backwards
|
||||
/// incompatible. For example, don't drop tables or columns.
|
||||
///
|
||||
/// The [`basic_migration`] function is a convenience function for specifying
|
||||
/// migrations that are simple SQL statements. If you need to do something
|
||||
/// more complex, then you can specify a closure that takes a [`Connection`]
|
||||
/// and does whatever is needed.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cargo::util::sqlite::*;
|
||||
/// # use rusqlite::Connection;
|
||||
/// # let mut conn = Connection::open_in_memory()?;
|
||||
/// # fn generate_name() -> String { "example".to_string() };
|
||||
/// migrate(
|
||||
/// &mut conn,
|
||||
/// &[
|
||||
/// basic_migration(
|
||||
/// "CREATE TABLE foo (
|
||||
/// id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/// name STRING NOT NULL
|
||||
/// )",
|
||||
/// ),
|
||||
/// Box::new(|conn| {
|
||||
/// conn.execute("INSERT INTO foo (name) VALUES (?1)", [generate_name()])?;
|
||||
/// Ok(())
|
||||
/// }),
|
||||
/// basic_migration("ALTER TABLE foo ADD COLUMN size INTEGER"),
|
||||
/// ],
|
||||
/// )?;
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub type Migration = Box<dyn Fn(&Connection) -> CargoResult<()>>;
|
||||
|
||||
/// A basic migration that is a single static SQL statement.
|
||||
///
|
||||
/// See [`Migration`] for more information.
|
||||
pub fn basic_migration(stmt: &'static str) -> Migration {
|
||||
Box::new(|conn| {
|
||||
conn.execute(stmt, [])?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Perform one-time SQL migrations.
|
||||
///
|
||||
/// See [`Migration`] for more information.
|
||||
pub fn migrate(conn: &mut Connection, migrations: &[Migration]) -> CargoResult<()> {
|
||||
// EXCLUSIVE ensures that it starts with an exclusive write lock. No other
|
||||
// readers will be allowed. This generally shouldn't be needed if there is
|
||||
// a file lock, but might be helpful in cases where cargo's `FileLock`
|
||||
// failed.
|
||||
let tx = conn.transaction_with_behavior(TransactionBehavior::Exclusive)?;
|
||||
let user_version = tx.query_row("SELECT user_version FROM pragma_user_version", [], |row| {
|
||||
row.get(0)
|
||||
})?;
|
||||
if user_version < migrations.len() {
|
||||
for migration in &migrations[user_version..] {
|
||||
migration(&tx)?;
|
||||
}
|
||||
tx.pragma_update(None, "user_version", &migrations.len())?;
|
||||
}
|
||||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn migrate_twice() -> CargoResult<()> {
|
||||
// Check that a second migration will apply.
|
||||
let mut conn = Connection::open_in_memory()?;
|
||||
let mut migrations = vec![basic_migration("CREATE TABLE foo (a, b, c)")];
|
||||
migrate(&mut conn, &migrations)?;
|
||||
conn.execute("INSERT INTO foo VALUES (1,2,3)", [])?;
|
||||
migrations.push(basic_migration("ALTER TABLE foo ADD COLUMN d"));
|
||||
migrate(&mut conn, &migrations)?;
|
||||
conn.execute("INSERT INTO foo VALUES (1,2,3,4)", [])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue