Archived
1
0
Fork 0

chore: add documentation for migra core crate

This commit is contained in:
Dmitriy Pleshevskiy 2021-06-13 01:09:46 +03:00
parent f913952df0
commit 7f478671b1
11 changed files with 244 additions and 19 deletions

View file

@ -1,8 +1,15 @@
[package]
name = "migra"
version = "0.1.0"
version = "1.0.0"
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
edition = "2018"
description = "Migra is a simple library for managing SQL in your application"
homepage = "https://github.com/pleshevskiy/migra"
repository = "https://github.com/pleshevskiy/migra"
license = "MIT OR Apache-2.0"
keywords = ["migration", "sql", "manager"]
categories = ["accessibility", "database"]
readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

55
migra/README.md Normal file
View file

@ -0,0 +1,55 @@
# Migra
[![CI](https://github.com/pleshevskiy/migra/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/pleshevskiy/migra/actions/workflows/rust.yml)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![Crates.io](https://img.shields.io/crates/v/migra)](https://crates.io/crates/migra)
![Crates.io](https://img.shields.io/crates/l/migra)
Migra is a simple library for managing SQL in your application.
For example, if you have a task list application, you can update the local user database from version to version.
This is main crate for [migra-cli](https://crates.io/crates/migra-cli), which allows you to manege SQL for web
servers in any program language without being bound to SQL frameworks.
### Installation
Add `migra = { version = "1.0" }` as a dependency in `Cargo.toml`.
This crate has not required predefined database clients in features with similar name.
If you want to add them, just install crate with additional features (`postgres`, `mysql`, `sqlite`).
`Cargo.toml` example:
```toml
[package]
name = "my-crate"
version = "0.1.0"
authors = ["Me <user@rust-lang.org>"]
[dependencies]
migra = { version = "1.0", features = ["postgres"] }
```
### Usage
For more information about the crate, please read doc.
### Supported databases
| Database | Feature |
|----------|--------------|
| Postgres | postgres |
| MySQL | mysql |
| Sqlite | sqlite |
## License
Licensed under either of these:
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE_APACHE) or
https://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE_MIT) or
https://opensource.org/licenses/MIT)

View file

@ -1,37 +1,39 @@
// #![deny(missing_debug_implementations)]
// #![deny(clippy::all, clippy::pedantic)]
// #![allow(clippy::module_name_repetitions)]
// #![allow(clippy::missing_errors_doc)]
use crate::errors::MigraResult;
use crate::managers::{ManageMigrations, ManageTransaction};
/// A trait that helps to open a connection to a specific database client.
pub trait OpenDatabaseConnection
where
Self: Sized,
{
/// Open database connection with predefined migrations table name.
fn new(connection_string: &str) -> MigraResult<Self> {
Self::manual(connection_string, "migrations")
}
/// Open database connection manually with additional migration table name parameter.
fn manual(connection_string: &str, migrations_table_name: &str) -> MigraResult<Self>;
}
/// All client implementations that have migration and transaction manager implementations
/// are considered clients.
pub trait Client: ManageMigrations + ManageTransaction {}
/// If you have complex application mechanics that allow users to choose which
/// database they can use, then you will most likely need this helper for that.
pub type AnyClient = Box<(dyn Client + 'static)>;
#[cfg(feature = "postgres")]
pub mod postgres;
mod postgres;
#[cfg(feature = "postgres")]
pub use self::postgres::Client as PostgresClient;
#[cfg(feature = "mysql")]
pub mod mysql;
mod mysql;
#[cfg(feature = "mysql")]
pub use self::mysql::Client as MysqlClient;
#[cfg(feature = "sqlite")]
pub mod sqlite;
mod sqlite;
#[cfg(feature = "sqlite")]
pub use self::sqlite::Client as SqliteClient;

View file

@ -5,6 +5,9 @@ use crate::migration;
use mysql::prelude::*;
use mysql::{Pool, PooledConn};
/// Predefined `MySQL` client.
///
/// **Note:** Requires enabling `mysql` feature.
#[derive(Debug)]
pub struct Client {
conn: PooledConn,
@ -12,6 +15,7 @@ pub struct Client {
}
impl Client {
/// Provide access to the original database connection.
#[must_use]
pub fn conn(&self) -> &PooledConn {
&self.conn

View file

@ -5,12 +5,16 @@ use crate::migration;
use postgres::{Client as PostgresClient, NoTls};
use std::fmt;
/// Predefined `Postgres` client.
///
/// **Note:** Requires enabling `postgres` feature.
pub struct Client {
conn: PostgresClient,
migrations_table_name: String,
}
impl Client {
/// Provide access to the original database connection.
#[must_use]
pub fn conn(&self) -> &PostgresClient {
&self.conn

View file

@ -4,6 +4,9 @@ use crate::managers::{BatchExecute, ManageMigrations, ManageTransaction};
use crate::migration;
use rusqlite::Connection;
/// Predefined `Sqlite` client.
///
/// **Note:** Requires enabling `sqlite` feature.
#[derive(Debug)]
pub struct Client {
conn: Connection,
@ -11,6 +14,7 @@ pub struct Client {
}
impl Client {
/// Provide access to the original database connection.
#[must_use]
pub fn conn(&self) -> &Connection {
&self.conn

View file

@ -1,13 +1,22 @@
use std::fmt;
use std::io;
/// A helper type for any standard error.
pub type StdError = Box<dyn std::error::Error + 'static + Sync + Send>;
/// A helper type for any result with standard error.
pub type StdResult<T> = Result<T, StdError>;
/// A helper type for any result with migra error.
pub type MigraResult<T> = Result<T, Error>;
/// Migra error
#[derive(Debug)]
pub enum Error {
/// Represents database errors.
Db(DbError),
/// Represents standard input output errors.
Io(io::Error),
}
@ -36,24 +45,41 @@ impl From<io::Error> for Error {
}
impl Error {
/// Creates a database error.
#[must_use]
pub fn db(origin: StdError, kind: DbKind) -> Self {
Error::Db(DbError { kind, origin })
}
}
/// All kinds of errors with witch this crate works.
#[derive(Debug)]
pub enum DbKind {
/// Failed to database connection.
DatabaseConnection,
/// Failed to open transaction.
OpenTransaction,
/// Failed to commit transaction.
CommitTransaction,
/// Failed to rollback transaction.
RollbackTransaction,
/// Failed to create a migrations table.
CreateMigrationsTable,
/// Failed to apply SQL.
ApplySql,
/// Failed to insert a migration.
InsertMigration,
/// Failed to delete a migration.
DeleteMigration,
/// Failed to get applied migrations.
GetAppliedMigrations,
}
@ -73,6 +99,7 @@ impl fmt::Display for DbKind {
}
}
/// Represents database error.
#[derive(Debug)]
pub struct DbError {
kind: DbKind,
@ -84,3 +111,17 @@ impl fmt::Display for DbError {
write!(fmt, "{} - {}", &self.kind, &self.origin)
}
}
impl DbError {
/// Returns database error kind.
#[must_use]
pub fn kind(&self) -> &DbKind {
&self.kind
}
/// Returns origin database error.
#[must_use]
pub fn origin(&self) -> &StdError {
&self.origin
}
}

View file

@ -3,11 +3,19 @@ use crate::migration;
use std::io;
use std::path::Path;
/// Checks if the directory is a migration according to the principles of the crate.
#[must_use]
pub fn is_migration_dir(path: &Path) -> bool {
path.join("up.sql").exists() && path.join("down.sql").exists()
}
/// Get all migration directories from path and returns as [List].
///
/// This utility checks if the directory is a migration. See [`is_migration_dir`] for
/// more information.
///
/// [List]: migration::List
/// [is_migration_dir]: fs::is_migration_dir
pub fn get_all_migrations(dir_path: &Path) -> MigraResult<migration::List> {
let mut entries = match dir_path.read_dir() {
Err(e) if e.kind() == io::ErrorKind::NotFound => vec![],
@ -24,11 +32,3 @@ pub fn get_all_migrations(dir_path: &Path) -> MigraResult<migration::List> {
entries.sort();
Ok(migration::List::from(entries))
}
#[must_use]
pub fn filter_pending_migrations(
all_migrations: &migration::List,
applied_migrations: &migration::List,
) -> migration::List {
all_migrations.exclude(applied_migrations)
}

View file

@ -1,13 +1,61 @@
//! # Migra
//!
//! Migra is a simple library for managing SQL in your application.
//!
//! For example, if you have a task list application, you can update the local user database from version to version.
//!
//! This is main crate for [migra-cli](https://crates.io/crates/migra-cli), which allows you to manege SQL for web
//! servers in any program language without being bound to SQL frameworks.
//!
//! ## Installation
//!
//! Add `migra = { version = "1.0" }` as a dependency in `Cargo.toml`.
//!
//! This crate has not required predefined database clients in features with similar name.
//! If you want to add them, just install crate with additional features (`postgres`, `mysql`, `sqlite`).
//!
//! `Cargo.toml` example:
//!
//! ```toml
//! [package]
//! name = "my-crate"
//! version = "0.1.0"
//! authors = ["Me <user@rust-lang.org>"]
//!
//! [dependencies]
//! migra = { version = "1.0", features = ["postgres"] }
//! ```
//!
//! ### Supported databases
//!
//! | Database Client | Feature |
//! |-----------------|--------------|
//! | `Postgres` | postgres |
//! | `MySQL` | mysql |
//! | `Sqlite` | sqlite |
//!
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]
#![deny(clippy::all, clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
/// Includes additional client tools and contains predefined
/// database clients that have been enabled in the features.
pub mod clients;
mod errors;
/// Includes all types of errors that uses in the crate.
pub mod errors;
/// Includes utilities that use the file system to work.
pub mod fs;
/// Includes all the basic traits that will allow you
/// to create your own client.
pub mod managers;
/// Includes basic structures of migration and migration
/// lists, that are used in managers and fs utils.
pub mod migration;
pub use errors::{Error, MigraResult as Result, StdResult};
pub use migration::Migration;
pub use migration::{List as MigrationList, Migration};

View file

@ -1,47 +1,71 @@
use crate::errors::{DbKind, Error, MigraResult, StdResult};
use crate::migration;
/// Used to execute SQL.
///
/// Is a super trait for managers.
pub trait BatchExecute {
/// Executes sql via original database client
fn batch_execute(&mut self, sql: &str) -> StdResult<()>;
}
/// Used to manage transaction in the database connection.
pub trait ManageTransaction: BatchExecute {
/// Opens transaction in database connection.
fn begin_transaction(&mut self) -> MigraResult<()> {
self.batch_execute("BEGIN")
.map_err(|err| Error::db(err, DbKind::OpenTransaction))
}
/// Cancels (Rollbacks) transaction in database connection.
fn rollback_transaction(&mut self) -> MigraResult<()> {
self.batch_execute("ROLLBACK")
.map_err(|err| Error::db(err, DbKind::RollbackTransaction))
}
/// Apply (Commit) transaction in database connection.
fn commit_transaction(&mut self) -> MigraResult<()> {
self.batch_execute("COMMIT")
.map_err(|err| Error::db(err, DbKind::CommitTransaction))
}
}
/// Used to manage migrations in the database connection.
pub trait ManageMigrations: BatchExecute {
/// Applies SQL. Similar to [`BatchExecute`], but returns migra [Error].
///
/// [BatchExecute]: managers::BatchExecute
fn apply_sql(&mut self, sql: &str) -> MigraResult<()> {
self.batch_execute(sql)
.map_err(|err| Error::db(err, DbKind::ApplySql))
}
/// Creates migration table.
fn create_migrations_table(&mut self) -> MigraResult<()>;
/// Inserts new migration to table.
fn insert_migration(&mut self, name: &str) -> MigraResult<u64>;
/// Deletes migration from table.
fn delete_migration(&mut self, name: &str) -> MigraResult<u64>;
/// Get applied migrations from table.
fn get_applied_migrations(&mut self) -> MigraResult<migration::List>;
/// Applies SQL to upgrade database schema and inserts new migration to table.
///
/// **Note:** Must be run in a transaction otherwise if the migration causes any
/// error the data in the database may be inconsistent.
fn run_upgrade_migration(&mut self, name: &str, content: &str) -> MigraResult<()> {
self.apply_sql(content)?;
self.insert_migration(name)?;
Ok(())
}
/// Applies SQL to downgrade database schema and deletes migration from table.
///
/// **Note:** Must be run in a transaction otherwise if the migration causes any
/// error the data in the database may be inconsistent.
fn run_downgrade_migration(&mut self, name: &str, content: &str) -> MigraResult<()> {
self.apply_sql(content)?;
self.delete_migration(name)?;

View file

@ -2,12 +2,14 @@ use crate::errors::MigraResult;
use crate::managers::ManageMigrations;
use std::iter::FromIterator;
/// A simple wrap over string.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Migration {
name: String,
}
impl Migration {
/// Creates new migration by name.
#[must_use]
pub fn new(name: &str) -> Self {
Migration {
@ -15,12 +17,21 @@ impl Migration {
}
}
/// Returns name of migration.
#[must_use]
pub fn name(&self) -> &String {
&self.name
}
}
/// Wrap over migration vector. Can be implicitly converted to a vector and has
/// a few of additional utilities for handling migrations.
///
/// Can be presented as a list of all migrations, a list of pending migrations
/// or a list of applied migrations, depending on the implementation.
///
///
///
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct List {
inner: Vec<Migration>,
@ -82,19 +93,38 @@ impl std::ops::Deref for List {
}
impl List {
/// Creates empty migration list.
#[must_use]
pub fn new() -> Self {
List { inner: Vec::new() }
}
/// Push migration to list.
pub fn push(&mut self, migration: Migration) {
self.inner.push(migration)
}
/// Push migration name to list.
///
/// # Example
///
/// ```rust
/// # let mut list = List::new();
/// list.push_name("name");
/// # assert_eq!(list, List::from(vec!["name"]));
/// ```
///
/// Is identical to the following
/// ```rust
/// # let mut list = List::new();
/// list.push(Migration::new("name"));
/// # assert_eq!(list, List::from(vec!["name"]));
/// ```
pub fn push_name(&mut self, name: &str) {
self.inner.push(Migration::new(name))
}
/// Check if list contains specific migration.
#[must_use]
pub fn contains(&self, other_migration: &Migration) -> bool {
self.inner
@ -102,11 +132,13 @@ impl List {
.any(|migration| migration == other_migration)
}
/// Check if list contains migration with specific name.
#[must_use]
pub fn contains_name(&self, name: &str) -> bool {
self.inner.iter().any(|migration| migration.name() == name)
}
/// Exclude specific list from current list.
#[must_use]
pub fn exclude(&self, list: &List) -> List {
self.inner
@ -115,6 +147,8 @@ impl List {
.collect()
}
/// Runs a upgrade migration with SQL content and adds a new migration to the current list
/// If there is no migration migration with specific name in the list.
pub fn should_run_upgrade_migration(
&mut self,
client: &mut dyn ManageMigrations,
@ -131,6 +165,8 @@ impl List {
Ok(is_missed)
}
/// Runs a downgrade migration with SQL content and removes the last migration from the
/// current list if the last item in the list has the specified name.
pub fn should_run_downgrade_migration(
&mut self,
client: &mut dyn ManageMigrations,