use itertools::Itertools;
use openvet_common::rust::CrateName;
use rusqlite::Connection;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
#[cfg(test)]
use test_strategy::Arbitrary;
use thiserror::Error;
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(from = "i64")]
pub struct Reference<T> {
value: i64,
target: PhantomData<T>,
}
impl<T> Reference<T> {
pub fn new(value: i64) -> Self {
Self {
value,
target: PhantomData,
}
}
}
impl<T> From<i64> for Reference<T> {
fn from(value: i64) -> Self {
Self::new(value)
}
}
#[derive(Error, Debug)]
pub enum InsertError {
#[error(transparent)]
Encoding(#[from] serde_rusqlite::Error),
#[error(transparent)]
Database(#[from] rusqlite::Error),
}
pub trait Insert: Serialize {
fn table(&self) -> &str;
fn insert_or_ignore(&self, connection: &Connection) -> Result<i64, InsertError> {
let params = serde_rusqlite::to_params_named(self)?;
let table = self.table();
let columns = params
.to_slice()
.iter()
.map(|(name, _)| &name[1..])
.join(", ");
let names = params
.to_slice()
.iter()
.map(|(name, _)| self.column(name).unwrap_or(name))
.join(", ");
let query = format!("INSERT OR IGNORE INTO {table}({columns}) VALUES ({names})");
connection.execute(&query, &*params.to_slice())?;
Ok(0)
}
fn insert_or_update(&self, connection: &Connection) -> Result<i64, InsertError> {
let params = serde_rusqlite::to_params_named(self)?;
let table = self.table();
let columns = params
.to_slice()
.iter()
.map(|(name, _)| &name[1..])
.join(", ");
let names = params
.to_slice()
.iter()
.map(|(name, _)| self.column(name).unwrap_or(name))
.join(", ");
let query = format!("INSERT OR UPDATE INTO {table}({columns}) VALUES ({names})");
connection.execute(&query, &*params.to_slice())?;
Ok(0)
}
fn insert(&self, connection: &Connection) -> Result<i64, InsertError> {
let params = serde_rusqlite::to_params_named(self)?;
let table = self.table();
let columns = params
.to_slice()
.iter()
.map(|(name, _)| &name[1..])
.join(", ");
let names = params
.to_slice()
.iter()
.map(|(name, _)| self.column(name).unwrap_or(name))
.join(", ");
let query = format!("INSERT INTO {table}({columns}) VALUES ({names})");
connection.execute(&query, &*params.to_slice())?;
Ok(0)
}
fn column(&self, name: &str) -> Option<&str> {
None
}
}
#[derive(Serialize, Deserialize)]
pub struct RustCrateRow {
id: Reference<Self>,
#[serde(rename = "crate")]
krate: CrateName,
}
#[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct InsertRustCrate {
pub name: CrateName,
}
impl Insert for InsertRustCrate {
fn table(&self) -> &str {
"rust_crates"
}
}
#[derive(Serialize, Deserialize)]
pub struct RustCrateVersionRow {
pub id: Reference<Self>,
#[serde(rename = "crate")]
pub krate: CrateName,
pub version: Version,
pub files: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct InsertRustCrateVersion {
#[serde(rename = "crate")]
pub krate: CrateName,
#[cfg_attr(test, strategy(openvet_common::proptest::version()))]
pub version: Version,
}
impl Insert for InsertRustCrateVersion {
fn table(&self) -> &str {
"rust_crate_versions"
}
fn column(&self, name: &str) -> Option<&str> {
match name {
":crate" => Some("(SELECT id FROM rust_crates WHERE name = :crate)"),
_ => None,
}
}
}