use crate::{
prelude::{Insertable, Queryable},
schema::entity::Entity,
Error, Transaction,
};
mod eval;
mod parse;
pub trait CLIError: From<Error> {
fn no_such_entity(ename: &'static str, query: String) -> Self;
}
impl CLIError for Error {
fn no_such_entity(_ename: &'static str, _query: String) -> Self {
Error::EmptyResult
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum ValueRole {
BaseTarget,
AttachmentTarget,
}
pub trait EntityInterface {
type Entity: Entity;
type Error: CLIError;
type Context;
type CustomCommand: clap::Subcommand + std::fmt::Debug;
fn run_custom(
ctx: &Self::Context,
cmd: Self::CustomCommand,
txn: &mut Transaction,
query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
) -> Result<(), Self::Error>;
fn summarize(_: &Self::Entity) -> Option<String> {
None
}
fn should_override(_entity: &'static str, _field: &'static str, _role: ValueRole) -> bool {
false
}
fn override_for(
_ctx: &Self::Context,
_entity: &'static str,
_field: &'static str,
_role: ValueRole,
) -> String {
unreachable!()
}
}
#[derive(Debug)]
pub struct EmptyCommand;
impl clap::FromArgMatches for EmptyCommand {
fn from_arg_matches(_matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
Err(clap::Error::new(clap::error::ErrorKind::UnknownArgument))
}
fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> {
Ok(())
}
}
impl clap::Subcommand for EmptyCommand {
fn augment_subcommands(cmd: clap::Command) -> clap::Command {
cmd
}
fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command {
cmd
}
fn has_subcommand(_name: &str) -> bool {
false
}
}
#[derive(Debug)]
pub struct Autogenerate<EI: EntityInterface> {
verb: parse::Verb<EI>,
_ghost: std::marker::PhantomData<(EI,)>,
}
#[cfg(test)]
mod tests {
use crate::ConnectionPool;
use super::{Autogenerate, EntityInterface};
use clap::Parser;
use microrm::prelude::*;
use test_log::test;
struct CTRelation;
impl Relation for CTRelation {
type Range = Customer;
type Domain = Transaction;
const NAME: &'static str = "CTRelation";
}
struct ETRelation;
impl Relation for ETRelation {
type Range = Employee;
type Domain = Transaction;
const NAME: &'static str = "ETRelation";
}
#[derive(Entity)]
struct Customer {
#[key]
name: String,
txs: microrm::RelationRange<CTRelation>,
}
#[derive(Entity)]
struct Employee {
#[key]
name: String,
txs: microrm::RelationRange<ETRelation>,
}
#[derive(Entity)]
struct Transaction {
#[key]
title: String,
amount: isize,
customer: microrm::RelationDomain<CTRelation>,
employee: microrm::RelationDomain<ETRelation>,
}
#[derive(Schema)]
struct TransactionTestDB {
customers: microrm::IDMap<Customer>,
employees: microrm::IDMap<Employee>,
transactions: microrm::IDMap<Transaction>,
}
#[derive(Debug, clap::Subcommand)]
enum CCustom {
Create { name: String },
}
#[derive(Debug)]
struct CustomerInterface;
impl EntityInterface for CustomerInterface {
type Entity = Customer;
type Error = microrm::Error;
type Context = ();
type CustomCommand = CCustom;
fn run_custom(
_ctx: &Self::Context,
cmd: Self::CustomCommand,
txn: &mut microrm::Transaction,
query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
) -> Result<(), Self::Error> {
match cmd {
CCustom::Create { name } => {
query_ctx.insert(
txn,
Customer {
name,
txs: Default::default(),
},
)?;
},
}
Ok(())
}
}
#[derive(Debug, clap::Subcommand)]
enum ECustom {
Create { name: String },
}
#[derive(Debug)]
struct EmployeeInterface;
impl EntityInterface for EmployeeInterface {
type Entity = Employee;
type Error = microrm::Error;
type Context = ();
type CustomCommand = ECustom;
fn run_custom(
_ctx: &Self::Context,
cmd: Self::CustomCommand,
txn: &mut microrm::Transaction,
query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
) -> Result<(), Self::Error> {
match cmd {
ECustom::Create { name } => {
query_ctx.insert(
txn,
Employee {
name,
txs: Default::default(),
},
)?;
},
}
Ok(())
}
}
#[derive(Debug, clap::Subcommand)]
enum TCustom {
Create { title: String, amount: isize },
}
#[derive(Debug)]
struct TransactionInterface;
impl EntityInterface for TransactionInterface {
type Entity = Transaction;
type Error = microrm::Error;
type Context = ();
type CustomCommand = TCustom;
fn run_custom(
_ctx: &Self::Context,
cmd: Self::CustomCommand,
txn: &mut microrm::Transaction,
query_ctx: impl Queryable<EntityOutput = Self::Entity> + Insertable<Self::Entity>,
) -> Result<(), Self::Error> {
match cmd {
TCustom::Create { title, amount } => {
query_ctx.insert(
txn,
Transaction {
title,
amount,
customer: Default::default(),
employee: Default::default(),
},
)?;
},
}
Ok(())
}
}
#[derive(Debug, clap::Parser)]
enum Params {
Customer {
#[clap(subcommand)]
cmd: Autogenerate<CustomerInterface>,
},
Employee {
#[clap(subcommand)]
cmd: Autogenerate<EmployeeInterface>,
},
Tx {
#[clap(subcommand)]
cmd: Autogenerate<TransactionInterface>,
},
}
fn run_cmd(txn: &mut microrm::Transaction, db: &TransactionTestDB, args: &[&str]) {
match <Params as Parser>::try_parse_from(args) {
Ok(Params::Customer { cmd }) => {
cmd.perform(&(), txn, &db.customers)
.expect("couldn't perform command");
},
Ok(Params::Employee { cmd }) => {
cmd.perform(&(), txn, &db.employees)
.expect("couldn't perform command");
},
Ok(Params::Tx { cmd }) => {
cmd.perform(&(), txn, &db.transactions)
.expect("couldn't perform command");
},
Err(e) => {
println!("{}", e.render());
panic!("error parsing arguments")
},
}
}
#[test]
fn simple_entity_create_delete() {
let (pool, db) = ConnectionPool::open::<TransactionTestDB>(":memory:").unwrap();
let mut txn = pool.start().unwrap();
assert_eq!(
db.customers
.keyed("a_key")
.count(&mut txn)
.expect("couldn't count entries"),
0
);
run_cmd(&mut txn, &db, &["execname", "customer", "create", "a_key"]);
assert_eq!(
db.customers
.keyed("a_key")
.count(&mut txn)
.expect("couldn't count entries"),
1
);
run_cmd(&mut txn, &db, &["execname", "customer", "delete", "a_key"]);
assert_eq!(
db.customers
.keyed("a_key")
.count(&mut txn)
.expect("couldn't count entries"),
0
);
}
#[test]
fn create_and_attach() {
let (pool, db) = ConnectionPool::open::<TransactionTestDB>(":memory:").unwrap();
let mut txn = pool.start().unwrap();
run_cmd(&mut txn, &db, &["execname", "customer", "create", "cname"]);
run_cmd(&mut txn, &db, &["execname", "employee", "create", "ename"]);
run_cmd(&mut txn, &db, &["execname", "tx", "create", "tname", "100"]);
run_cmd(
&mut txn,
&db,
&["execname", "customer", "attach", "cname", "txs", "tname"],
);
run_cmd(
&mut txn,
&db,
&["execname", "employee", "attach", "ename", "txs", "tname"],
);
assert_eq!(
db.customers
.keyed("cname")
.join(Customer::Txs)
.join(Transaction::Employee)
.first()
.get(&mut txn)
.expect("couldn't run query")
.expect("no such employee")
.name,
"ename"
);
}
}