Initial commit
This commit is contained in:
commit
07785918c1
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
/.vscode
|
||||
/*.db
|
||||
Cargo.lock
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -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"
|
17
src/db.rs
Normal file
17
src/db.rs
Normal file
@ -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
|
||||
}
|
27
src/main.rs
Normal file
27
src/main.rs
Normal file
@ -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(())
|
||||
}
|
122
src/migrations.rs
Normal file
122
src/migrations.rs
Normal file
@ -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(())
|
||||
}
|
||||
}
|
1
src/model/mod.rs
Normal file
1
src/model/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
mod section;
|
21
src/model/section.rs
Normal file
21
src/model/section.rs
Normal file
@ -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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
10
src/section_interface.rs
Normal file
10
src/section_interface.rs
Normal file
@ -0,0 +1,10 @@
|
||||
pub type SectionId = u32;
|
||||
|
||||
pub trait SectionInterface {
|
||||
fn num_sections() -> SectionId;
|
||||
fn set_section(id: SectionId, running: bool);
|
||||
fn get_section(id: SectionId) -> bool;
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user