Alex Mikhalev
5 years ago
commit
07785918c1
8 changed files with 217 additions and 0 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
/target |
||||
/.vscode |
||||
/*.db |
||||
Cargo.lock |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
[package] |
||||
name = "sprinklers_rs" |
||||
version = "0.1.0" |
||||
authors = ["Alex Mikhalev <alexmikhalevalex@gmail.com>"] |
||||
edition = "2018" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
rusqlite = "0.23.1" |
||||
log = "0.4.11" |
||||
env_logger = "0.7.1" |
||||
color-eyre = "0.5.1" |
||||
eyre = "0.6.0" |
||||
thiserror = "1.0.20" |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
use crate::migrations::{Migrations, SimpleMigration}; |
||||
|
||||
pub fn create_migrations() -> Migrations { |
||||
let mut migs = Migrations::new(); |
||||
migs.add( |
||||
1, |
||||
SimpleMigration::new_box( |
||||
"CREATE TABLE sections ( |
||||
id INTEGER PRIMARY KEY, |
||||
name TEXT NOT NULL, |
||||
interface_id INTEGER NOT NULL |
||||
);", |
||||
"DROP TABLE sections;", |
||||
), |
||||
); |
||||
migs |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
use rusqlite::NO_PARAMS; |
||||
use rusqlite::Connection as DbConnection; |
||||
use color_eyre::eyre::Result; |
||||
|
||||
mod section_interface; |
||||
mod model; |
||||
mod db; |
||||
mod migrations; |
||||
|
||||
fn setup_db() -> Result<()> { |
||||
// let conn = DbConnection::open_in_memory()?;
|
||||
let mut conn = DbConnection::open("test.db")?; |
||||
|
||||
let migs = db::create_migrations(); |
||||
migs.apply(&mut conn)?; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
fn main() -> Result<()> { |
||||
env_logger::init(); |
||||
color_eyre::install()?; |
||||
println!("Hello, world!"); |
||||
setup_db().unwrap(); |
||||
|
||||
Ok(()) |
||||
} |
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
use rusqlite::NO_PARAMS; |
||||
use rusqlite::{params, Connection}; |
||||
use std::collections::BTreeMap; |
||||
use std::ops::Bound::{Excluded, Unbounded}; |
||||
use thiserror::Error; |
||||
use log::debug; |
||||
|
||||
#[derive(Debug, Error)] |
||||
pub enum MigrationError { |
||||
#[error("sql error: {0}")] |
||||
SqlError(#[from] rusqlite::Error), |
||||
#[error("database version {0} too new to migrate")] |
||||
VersionTooNew(MigrationVersion), |
||||
} |
||||
|
||||
pub type MigrationResult<T> = Result<T, MigrationError>; |
||||
|
||||
pub trait Migration { |
||||
fn up(&self, conn: &Connection) -> MigrationResult<()>; |
||||
fn down(&self, conn: &Connection) -> MigrationResult<()>; |
||||
} |
||||
|
||||
pub struct SimpleMigration { |
||||
pub up_sql: String, |
||||
pub down_sql: String, |
||||
} |
||||
|
||||
impl SimpleMigration { |
||||
pub fn new<T1: ToString, T2: ToString>(up_sql: T1, down_sql: T2) -> Self { |
||||
Self { |
||||
up_sql: up_sql.to_string(), |
||||
down_sql: down_sql.to_string(), |
||||
} |
||||
} |
||||
|
||||
pub fn new_box<T1: ToString, T2: ToString>(up_sql: T1, down_sql: T2) -> Box<dyn Migration> { |
||||
Box::new(Self::new(up_sql, down_sql)) |
||||
} |
||||
} |
||||
|
||||
impl Migration for SimpleMigration { |
||||
fn up(&self, conn: &Connection) -> MigrationResult<()> { |
||||
conn.execute(&self.up_sql, NO_PARAMS)?; |
||||
Ok(()) |
||||
} |
||||
fn down(&self, conn: &Connection) -> MigrationResult<()> { |
||||
conn.execute(&self.down_sql, NO_PARAMS)?; |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub type MigrationVersion = u32; |
||||
pub const NO_MIGRATIONS: MigrationVersion = 0; |
||||
|
||||
pub fn get_db_version(conn: &Connection) -> MigrationResult<MigrationVersion> { |
||||
let table_count: u32 = conn.query_row( |
||||
"SELECT COUNT(*) FROM sqlite_master |
||||
WHERE type='table' AND name='db_version'", |
||||
NO_PARAMS, |
||||
|row| row.get(0), |
||||
)?; |
||||
if table_count == 0 { |
||||
return Ok(NO_MIGRATIONS); |
||||
} |
||||
|
||||
let version: u32 = conn.query_row( |
||||
"SELECT version FROM db_version WHERE id = 1", |
||||
NO_PARAMS, |
||||
|row| row.get(0), |
||||
)?; |
||||
Ok(version) |
||||
} |
||||
|
||||
pub fn set_db_version(conn: &Connection, version: MigrationVersion) -> MigrationResult<()> { |
||||
conn.execute( |
||||
" |
||||
CREATE TABLE IF NOT EXISTS db_version ( |
||||
id INTEGER PRIMARY KEY, |
||||
version INTEGER |
||||
);", NO_PARAMS)?; |
||||
conn.execute( |
||||
" |
||||
INSERT OR REPLACE INTO db_version (id, version) |
||||
VALUES (1, ?1);", |
||||
params![version])?; |
||||
Ok(()) |
||||
} |
||||
|
||||
pub struct Migrations { |
||||
migrations: BTreeMap<MigrationVersion, Box<dyn Migration>>, |
||||
} |
||||
|
||||
impl Migrations { |
||||
pub fn new() -> Self { |
||||
Self { |
||||
migrations: BTreeMap::new(), |
||||
} |
||||
} |
||||
|
||||
pub fn add(&mut self, version: MigrationVersion, migration: Box<dyn Migration>) { |
||||
self.migrations.insert(version, migration); |
||||
} |
||||
|
||||
pub fn apply(&self, conn: &mut Connection) -> MigrationResult<()> { |
||||
let db_version = get_db_version(conn)?; |
||||
if db_version != 0 && !self.migrations.contains_key(&db_version) { |
||||
return Err(MigrationError::VersionTooNew(db_version)); |
||||
} |
||||
let mig_range = self.migrations.range( |
||||
(Excluded(db_version), Unbounded)); |
||||
let mut trans = conn.transaction()?; |
||||
let mut last_ver: MigrationVersion = 0; |
||||
for (ver, mig) in mig_range { |
||||
debug!("applying migration version {}", ver); |
||||
mig.up(&mut trans)?; |
||||
last_ver = *ver; |
||||
} |
||||
set_db_version(&trans, last_ver)?; |
||||
trans.commit()?; |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
use crate::section_interface::SectionId; |
||||
use rusqlite::Row; |
||||
use std::convert::TryFrom; |
||||
|
||||
pub struct Section { |
||||
pub id: u32, |
||||
pub name: String, |
||||
pub interface_id: SectionId, |
||||
} |
||||
|
||||
impl<'a> TryFrom<&Row<'a>> for Section { |
||||
type Error = rusqlite::Error; |
||||
fn try_from(row: &Row<'a>) -> Result<Section, Self::Error> { |
||||
Ok(Section { |
||||
id: row.get(0)?, |
||||
name: row.get(1)?, |
||||
interface_id: row.get(2)?, |
||||
}) |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue