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