699 lines
24 KiB
Rust
699 lines
24 KiB
Rust
use crate::addressing::Address;
|
|
use crate::hash::{decode, hash, Hash, Hashable};
|
|
use crate::models;
|
|
use crate::schema::data;
|
|
use crate::util::LoggerSink;
|
|
use anyhow::{anyhow, Result};
|
|
use diesel::debug_query;
|
|
use diesel::expression::operators::{And, Or};
|
|
use diesel::prelude::*;
|
|
use diesel::r2d2::{self, ConnectionManager};
|
|
use diesel::sql_types::Bool;
|
|
use diesel::sqlite::{Sqlite, SqliteConnection};
|
|
use lexpr::value::Value::Symbol;
|
|
use lexpr::Value::Cons;
|
|
use log::{debug, trace};
|
|
use nonempty::NonEmpty;
|
|
use serde_json::json;
|
|
use std::borrow::Borrow;
|
|
use std::convert::TryFrom;
|
|
use std::fs;
|
|
use std::io::{Cursor, Write};
|
|
use std::path::{Path, PathBuf};
|
|
use std::str::FromStr;
|
|
use std::time::Duration;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Entry {
|
|
pub identity: Hash,
|
|
pub target: Address,
|
|
pub key: String,
|
|
pub value: EntryValue,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct InnerEntry {
|
|
pub target: Address,
|
|
pub key: String,
|
|
pub value: EntryValue,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum EntryValue {
|
|
Value(serde_json::Value),
|
|
Address(Address),
|
|
Invalid,
|
|
}
|
|
|
|
impl Entry {
|
|
pub fn as_json(&self) -> serde_json::Value {
|
|
json!({
|
|
"target": self.target.to_string(),
|
|
"key": self.key,
|
|
"value": match &self.value {
|
|
EntryValue::Value(value) => ("VALUE", value.clone()),
|
|
EntryValue::Address(address) => ("ADDR", json!(address.to_string())),
|
|
EntryValue::Invalid => ("INVALID", json!("INVALID")),
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<models::Entry> for Entry {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(e: models::Entry) -> Result<Self, Self::Error> {
|
|
Ok(Entry {
|
|
identity: Hash(e.identity),
|
|
target: Address::decode(&e.target)?,
|
|
key: e.key,
|
|
value: e.value.parse().unwrap(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for InnerEntry {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{} | {} | {}", self.target, self.key, self.value)
|
|
}
|
|
}
|
|
|
|
impl Hashable for InnerEntry {
|
|
fn hash(self: &InnerEntry) -> Result<Hash> {
|
|
let mut result = Cursor::new(vec![0u8; 0]);
|
|
result.write_all(self.target.encode()?.as_slice())?;
|
|
result.write_all(self.key.as_bytes())?;
|
|
result.write_all(self.value.to_str()?.as_bytes())?;
|
|
Ok(hash(result.get_ref()))
|
|
}
|
|
}
|
|
|
|
impl EntryValue {
|
|
pub fn to_str(&self) -> Result<String> {
|
|
let (type_char, content) = match self {
|
|
EntryValue::Value(value) => ('J', serde_json::to_string(value)?),
|
|
EntryValue::Address(address) => ('O', address.to_string()),
|
|
EntryValue::Invalid => return Err(anyhow!("Cannot serialize invalid Entity value.")),
|
|
};
|
|
|
|
Ok(format!("{}{}", type_char, content))
|
|
}
|
|
}
|
|
|
|
// unsafe unwraps!
|
|
impl std::fmt::Display for EntryValue {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
let (entry_type, entry_value) = match self {
|
|
EntryValue::Address(address) => ("ADDRESS", address.to_string()),
|
|
EntryValue::Value(value) => ("VALUE", serde_json::to_string(value).unwrap()),
|
|
EntryValue::Invalid => ("INVALID", "INVALID".to_string()),
|
|
};
|
|
write!(f, "{}: {}", entry_type, entry_value)
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for EntryValue {
|
|
type Err = std::convert::Infallible;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if s.len() < 2 {
|
|
Ok(EntryValue::Invalid)
|
|
} else {
|
|
let (type_char, content) = s.split_at(1);
|
|
match (type_char, content) {
|
|
("J", content) => {
|
|
if let Ok(value) = serde_json::from_str(content) {
|
|
Ok(EntryValue::Value(value))
|
|
} else {
|
|
Ok(EntryValue::Invalid)
|
|
}
|
|
}
|
|
("O", content) => {
|
|
if let Ok(addr) = decode(content).and_then(|v| Address::decode(&v)) {
|
|
Ok(EntryValue::Address(addr))
|
|
} else {
|
|
Ok(EntryValue::Invalid)
|
|
}
|
|
}
|
|
_ => Ok(EntryValue::Invalid),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn insert_file<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
file: models::NewFile,
|
|
) -> Result<usize> {
|
|
use crate::schema::files;
|
|
|
|
debug!(
|
|
"Inserting {} ({})...",
|
|
&file.path,
|
|
Address::Hash(Hash((&file.hash).clone()))
|
|
);
|
|
|
|
Ok(diesel::insert_into(files::table)
|
|
.values(file)
|
|
.execute(connection)?)
|
|
}
|
|
|
|
pub fn retrieve_all_files<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
) -> Result<Vec<models::File>> {
|
|
use crate::schema::files::dsl::*;
|
|
let matches = files.load::<models::File>(connection)?;
|
|
Ok(matches)
|
|
}
|
|
|
|
pub fn retrieve_file<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
obj_hash: Hash,
|
|
) -> Result<Option<String>> {
|
|
use crate::schema::files::dsl::*;
|
|
|
|
let matches = files
|
|
.filter(valid.eq(true))
|
|
.filter(hash.eq(obj_hash.0))
|
|
.load::<models::File>(connection)?;
|
|
|
|
Ok(matches.get(0).map(|f| f.path.clone()))
|
|
}
|
|
|
|
pub fn file_set_valid<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
file_id: i32,
|
|
is_valid: bool,
|
|
) -> Result<usize> {
|
|
use crate::schema::files::dsl::*;
|
|
|
|
debug!("Setting file ID {} to valid = {}", file_id, is_valid);
|
|
|
|
Ok(diesel::update(files.filter(id.eq(file_id)))
|
|
.set(valid.eq(is_valid))
|
|
.execute(connection)?)
|
|
}
|
|
|
|
pub fn retrieve_object<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
object_address: Address,
|
|
) -> Result<Vec<Entry>> {
|
|
use crate::schema::data::dsl::*;
|
|
|
|
let matches = data
|
|
.filter(target.eq(object_address.encode()?))
|
|
.or_filter(value.eq(EntryValue::Address(object_address).to_str()?))
|
|
.load::<models::Entry>(connection)?;
|
|
let entries = matches
|
|
.into_iter()
|
|
.map(Entry::try_from)
|
|
.filter_map(Result::ok)
|
|
.collect();
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
pub fn bulk_retrieve_objects<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
object_addresses: Vec<Address>,
|
|
) -> Result<Vec<Entry>> {
|
|
use crate::schema::data::dsl::*;
|
|
|
|
let matches = data
|
|
.filter(
|
|
target.eq_any(
|
|
object_addresses
|
|
.iter()
|
|
.filter_map(|addr| addr.encode().ok()),
|
|
),
|
|
)
|
|
// .or_filter(value.eq(EntryValue::Address(object_address).to_str()?))
|
|
.load::<models::Entry>(connection)?;
|
|
let entries = matches
|
|
.into_iter()
|
|
.map(Entry::try_from)
|
|
.filter_map(Result::ok)
|
|
.collect();
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
pub fn remove_object<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
object_address: Address,
|
|
) -> Result<usize> {
|
|
use crate::schema::data::dsl::*;
|
|
|
|
debug!("Deleting {}!", object_address);
|
|
|
|
let matches = data
|
|
.filter(target.eq(object_address.encode()?))
|
|
.or_filter(value.eq(EntryValue::Address(object_address).to_str()?));
|
|
|
|
Ok(diesel::delete(matches).execute(connection)?)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum QueryComponent<T>
|
|
where
|
|
T: FromStr,
|
|
{
|
|
Exact(T),
|
|
In(Vec<T>),
|
|
Contains(String),
|
|
Any,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct EntryQuery {
|
|
pub entity: QueryComponent<Address>,
|
|
pub attribute: QueryComponent<String>,
|
|
pub value: QueryComponent<EntryValue>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum QueryPart {
|
|
Matches(EntryQuery),
|
|
Type(String),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum QueryQualifier {
|
|
AND,
|
|
OR,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct MultiQuery {
|
|
pub qualifier: QueryQualifier,
|
|
pub queries: NonEmpty<Box<Query>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Query {
|
|
SingleQuery(QueryPart),
|
|
MultiQuery(MultiQuery),
|
|
}
|
|
|
|
impl Query {
|
|
pub fn from_sexp(expression: &lexpr::Value) -> Result<Self> {
|
|
if let Cons(value) = expression {
|
|
if let Symbol(symbol) = value.car() {
|
|
match symbol.borrow() {
|
|
"matches" => {
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
if let [_, entity, attribute, value] = &cons_vec[..] {
|
|
let entity = Query::parse_component::<Address>(entity)?;
|
|
let attribute = Query::parse_component::<String>(attribute)?;
|
|
let value = Query::parse_component::<EntryValue>(value)?;
|
|
Ok(Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
entity,
|
|
attribute,
|
|
value,
|
|
})))
|
|
} else {
|
|
Err(anyhow!(
|
|
"Malformed expression: Wrong number of arguments to 'matches'."
|
|
))
|
|
}
|
|
}
|
|
"type" => {
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
if let [_, type_name] = &cons_vec[..] {
|
|
if let lexpr::Value::String(type_name_str) = type_name {
|
|
Ok(Query::SingleQuery(QueryPart::Type(
|
|
type_name_str.to_string(),
|
|
)))
|
|
} else {
|
|
Err(anyhow!(
|
|
"Malformed expression: Type must be specified as a string."
|
|
))
|
|
}
|
|
} else {
|
|
Err(anyhow!(
|
|
"Malformed expression: Wrong number of arguments to 'type'."
|
|
))
|
|
}
|
|
}
|
|
"and" | "or" => {
|
|
let (cons_vec, _) = value.clone().into_vec();
|
|
let sub_expressions = &cons_vec[1..];
|
|
let values: Result<Vec<Box<Query>>> = sub_expressions
|
|
.iter()
|
|
.map(|value| Ok(Box::new(Query::from_sexp(value)?)))
|
|
.collect();
|
|
|
|
if let Some(queries) = NonEmpty::from_vec(values?) {
|
|
Ok(Query::MultiQuery(MultiQuery {
|
|
qualifier: match symbol.borrow() {
|
|
"and" => QueryQualifier::AND,
|
|
_ => QueryQualifier::OR,
|
|
},
|
|
queries,
|
|
}))
|
|
} else {
|
|
Err(anyhow!(
|
|
"Malformed expression: sub-query list cannot be empty.",
|
|
))
|
|
}
|
|
}
|
|
_ => Err(anyhow!(format!(
|
|
"Malformed expression: Unknown symbol '{}'.",
|
|
symbol
|
|
))),
|
|
}
|
|
} else {
|
|
Err(anyhow!(format!(
|
|
"Malformed expression: Value '{:?}' is not a symbol.",
|
|
value
|
|
)))
|
|
}
|
|
} else {
|
|
Err(anyhow!("Malformed expression: Not a list."))
|
|
}
|
|
}
|
|
|
|
fn parse_component<T: FromStr>(value: &lexpr::Value) -> Result<QueryComponent<T>> {
|
|
match value {
|
|
Cons(cons) => {
|
|
if let Symbol(symbol) = cons.car() {
|
|
match symbol.borrow() {
|
|
"in" => {
|
|
let (cons_vec, _) = cons.clone().into_vec();
|
|
if let Some(split) = cons_vec.split_first() {
|
|
let args = split.1;
|
|
let values: Result<Vec<T>, _> = args.iter().map(|value| {
|
|
if let lexpr::Value::String(str) = value {
|
|
if let Ok(value) = T::from_str(str.borrow()) {
|
|
Ok(value)
|
|
} else {
|
|
Err(anyhow!(format!("Malformed expression: Conversion of inner value '{}' from string failed.", str)))
|
|
}
|
|
} else {
|
|
Err(anyhow!("Malformed expression: Inner value list must be comprised of strings."))
|
|
}
|
|
}).collect();
|
|
|
|
Ok(QueryComponent::In(values?))
|
|
} else {
|
|
Err(anyhow!(
|
|
"Malformed expression: Inner value cannot be empty."
|
|
))
|
|
}
|
|
}
|
|
"contains" => {
|
|
let (mut cons_vec, _) = cons.clone().into_vec();
|
|
match cons_vec.len() {
|
|
2 => {
|
|
let value = cons_vec.remove(1);
|
|
if let lexpr::Value::String(str) = value {
|
|
Ok(QueryComponent::Contains(str.into_string()))
|
|
} else {
|
|
Err(anyhow!("Malformed expression: 'Contains' argument must be a string."))
|
|
}
|
|
}
|
|
_ => Err(anyhow!(
|
|
"Malformed expression: 'Contains' requires a single argument."
|
|
)),
|
|
}
|
|
}
|
|
_ => Err(anyhow!(format!(
|
|
"Malformed expression: Unknown symbol {}",
|
|
symbol
|
|
))),
|
|
}
|
|
} else {
|
|
Err(anyhow!(format!(
|
|
"Malformed expression: Inner value '{:?}' is not a symbol.",
|
|
value
|
|
)))
|
|
}
|
|
}
|
|
lexpr::Value::String(str) => {
|
|
if let Ok(value) = T::from_str(str.borrow()) {
|
|
Ok(QueryComponent::Exact(value))
|
|
} else {
|
|
Err(anyhow!(format!(
|
|
"Malformed expression: Conversion of inner value '{}' from string failed.",
|
|
str
|
|
)))
|
|
}
|
|
}
|
|
lexpr::Value::Symbol(symbol) => match symbol.borrow() {
|
|
"?" => Ok(QueryComponent::Any),
|
|
_ => Err(anyhow!(format!(
|
|
"Malformed expression: Unknown symbol {}",
|
|
symbol
|
|
))),
|
|
},
|
|
_ => Err(anyhow!(
|
|
"Malformed expression: Inner value not a string, list or '?'."
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn query<C: Connection<Backend = Sqlite>>(connection: &C, query: Query) -> Result<Vec<Entry>> {
|
|
use crate::schema::data::dsl::*;
|
|
|
|
let db_query = data.filter(query_to_sqlite(&query)?);
|
|
|
|
trace!("Querying: {}", debug_query(&db_query));
|
|
|
|
let matches = db_query.load::<models::Entry>(connection)?;
|
|
|
|
let entries = matches
|
|
.into_iter()
|
|
.map(Entry::try_from)
|
|
.filter_map(Result::ok)
|
|
.collect();
|
|
|
|
Ok(entries)
|
|
}
|
|
type Predicate = dyn BoxableExpression<data::table, Sqlite, SqlType = Bool>;
|
|
|
|
fn query_to_sqlite(query: &Query) -> Result<Box<Predicate>> {
|
|
match query {
|
|
Query::SingleQuery(qp) => match qp {
|
|
QueryPart::Matches(eq) => {
|
|
let mut subqueries: Vec<Box<Predicate>> = vec![];
|
|
|
|
match &eq.entity {
|
|
QueryComponent::Exact(q_target) => {
|
|
subqueries.push(Box::new(data::target.eq(q_target.encode()?)))
|
|
}
|
|
QueryComponent::In(q_targets) => {
|
|
let targets: Result<Vec<_>, _> =
|
|
q_targets.iter().map(|t| t.encode()).collect();
|
|
subqueries.push(Box::new(data::target.eq_any(targets?)))
|
|
}
|
|
QueryComponent::Contains(_) => {
|
|
return Err(anyhow!("Addresses cannot be queried alike."))
|
|
}
|
|
QueryComponent::Any => {}
|
|
};
|
|
|
|
match &eq.attribute {
|
|
QueryComponent::Exact(q_key) => {
|
|
subqueries.push(Box::new(data::key.eq(q_key.clone())))
|
|
}
|
|
QueryComponent::In(q_keys) => {
|
|
subqueries.push(Box::new(data::key.eq_any(q_keys.clone())))
|
|
}
|
|
QueryComponent::Contains(q_key) => {
|
|
subqueries.push(Box::new(data::key.like(format!("%{}%", q_key))))
|
|
}
|
|
QueryComponent::Any => {}
|
|
};
|
|
|
|
match &eq.value {
|
|
QueryComponent::Exact(q_value) => {
|
|
subqueries.push(Box::new(data::value.eq(q_value.to_str()?)))
|
|
}
|
|
QueryComponent::In(q_values) => {
|
|
let values: Result<Vec<_>, _> =
|
|
q_values.iter().map(|v| v.to_str()).collect();
|
|
subqueries.push(Box::new(data::value.eq_any(values?)))
|
|
}
|
|
QueryComponent::Contains(q_value) => {
|
|
subqueries.push(Box::new(data::value.like(format!("%{}%", q_value))))
|
|
}
|
|
QueryComponent::Any => {}
|
|
};
|
|
|
|
match subqueries.len() {
|
|
0 => Ok(Box::new(true.into_sql::<Bool>())),
|
|
1 => Ok(subqueries.remove(0)),
|
|
_ => {
|
|
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
|
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
|
while !subqueries.is_empty() {
|
|
result = Box::new(And::new(result, subqueries.remove(0)));
|
|
}
|
|
Ok(Box::new(result))
|
|
}
|
|
}
|
|
}
|
|
QueryPart::Type(_) => unimplemented!("Type queries are not yet implemented."),
|
|
},
|
|
Query::MultiQuery(mq) => {
|
|
let subqueries: Result<Vec<Box<Predicate>>> =
|
|
mq.queries.iter().map(|sq| query_to_sqlite(sq)).collect();
|
|
let mut subqueries: Vec<Box<Predicate>> = subqueries?;
|
|
match subqueries.len() {
|
|
0 => Ok(Box::new(true.into_sql::<Bool>())),
|
|
1 => Ok(subqueries.remove(0)),
|
|
_ => match mq.qualifier {
|
|
QueryQualifier::AND => {
|
|
let mut result: Box<And<Box<Predicate>, Box<Predicate>>> =
|
|
Box::new(And::new(subqueries.remove(0), subqueries.remove(0)));
|
|
while !subqueries.is_empty() {
|
|
result = Box::new(And::new(result, subqueries.remove(0)));
|
|
}
|
|
Ok(Box::new(result))
|
|
}
|
|
QueryQualifier::OR => {
|
|
let mut result: Box<Or<Box<Predicate>, Box<Predicate>>> =
|
|
Box::new(Or::new(subqueries.remove(0), subqueries.remove(0)));
|
|
while !subqueries.is_empty() {
|
|
result = Box::new(Or::new(result, subqueries.remove(0)));
|
|
}
|
|
Ok(Box::new(result))
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn query_entries<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
entry_query: EntryQuery,
|
|
) -> Result<Vec<Entry>> {
|
|
use crate::schema::data::dsl::*;
|
|
|
|
let mut query = data.into_boxed();
|
|
|
|
query = match entry_query.entity {
|
|
QueryComponent::Exact(q_target) => query.filter(target.eq(q_target.encode()?)),
|
|
QueryComponent::In(q_targets) => {
|
|
let targets: Result<Vec<_>, _> = q_targets.into_iter().map(|t| t.encode()).collect();
|
|
query.filter(target.eq_any(targets?))
|
|
}
|
|
QueryComponent::Contains(_) => return Err(anyhow!("Cannot query Address alike.")),
|
|
QueryComponent::Any => query,
|
|
};
|
|
|
|
query = match entry_query.attribute {
|
|
QueryComponent::Exact(q_key) => query.filter(key.eq(q_key)),
|
|
QueryComponent::In(q_keys) => query.filter(key.eq_any(q_keys)),
|
|
QueryComponent::Contains(q_key) => query.filter(key.like(format!("%{}%", q_key))),
|
|
QueryComponent::Any => query,
|
|
};
|
|
|
|
query = match entry_query.value {
|
|
QueryComponent::Exact(q_value) => query.filter(value.eq(q_value.to_str()?)),
|
|
QueryComponent::In(q_values) => {
|
|
let values: Result<Vec<_>, _> = q_values.into_iter().map(|v| v.to_str()).collect();
|
|
query.filter(value.eq_any(values?))
|
|
}
|
|
QueryComponent::Contains(q_value_string) => {
|
|
query.filter(value.like(format!("%{}%", q_value_string)))
|
|
}
|
|
QueryComponent::Any => query,
|
|
};
|
|
|
|
trace!("Querying: {}", debug_query(&query));
|
|
|
|
let matches = query.load::<models::Entry>(connection)?;
|
|
|
|
let entries = matches
|
|
.into_iter()
|
|
.map(Entry::try_from)
|
|
.filter_map(Result::ok)
|
|
.collect();
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
pub fn insert_entry<C: Connection<Backend = Sqlite>>(
|
|
connection: &C,
|
|
entry: InnerEntry,
|
|
) -> Result<usize> {
|
|
debug!("Inserting: {}", entry);
|
|
|
|
let insert_entry = models::Entry {
|
|
identity: entry.hash()?.0,
|
|
target: entry.target.encode()?,
|
|
key: entry.key,
|
|
value: entry.value.to_str()?,
|
|
};
|
|
|
|
Ok(diesel::insert_into(data::table)
|
|
.values(insert_entry)
|
|
.execute(connection)?)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ConnectionOptions {
|
|
pub enable_foreign_keys: bool,
|
|
pub busy_timeout: Option<Duration>,
|
|
}
|
|
|
|
impl ConnectionOptions {
|
|
pub fn apply(&self, conn: &SqliteConnection) -> QueryResult<()> {
|
|
if self.enable_foreign_keys {
|
|
conn.execute("PRAGMA foreign_keys = ON;")?;
|
|
}
|
|
if let Some(duration) = self.busy_timeout {
|
|
conn.execute(&format!("PRAGMA busy_timeout = {};", duration.as_millis()))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl diesel::r2d2::CustomizeConnection<SqliteConnection, diesel::r2d2::Error>
|
|
for ConnectionOptions
|
|
{
|
|
fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> {
|
|
self.apply(conn).map_err(diesel::r2d2::Error::QueryError)
|
|
}
|
|
}
|
|
|
|
pub type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
|
|
|
|
pub struct OpenResult {
|
|
pub pool: DbPool,
|
|
pub new: bool,
|
|
}
|
|
|
|
pub const DATABASE_FILENAME: &str = "upend.sqlite3";
|
|
|
|
pub fn open_upend<P: AsRef<Path>>(dirpath: P, reinitialize: bool) -> Result<OpenResult> {
|
|
embed_migrations!("./migrations/upend/");
|
|
|
|
let database_path: PathBuf = dirpath.as_ref().join(DATABASE_FILENAME);
|
|
if reinitialize {
|
|
let _ = fs::remove_file(&database_path);
|
|
}
|
|
let new = !database_path.exists();
|
|
|
|
let manager = ConnectionManager::<SqliteConnection>::new(database_path.to_str().unwrap());
|
|
let pool = r2d2::Pool::builder()
|
|
.connection_customizer(Box::new(ConnectionOptions {
|
|
enable_foreign_keys: true,
|
|
busy_timeout: Some(Duration::from_secs(30)),
|
|
}))
|
|
.build(manager)
|
|
.expect("Failed to create pool.");
|
|
|
|
embedded_migrations::run_with_output(
|
|
&pool.get().unwrap(),
|
|
&mut LoggerSink {
|
|
..Default::default()
|
|
},
|
|
)?;
|
|
|
|
Ok(OpenResult { pool, new })
|
|
}
|