Entity manipulation CLI
One optional feature that microrm supports is autogeneration of a CLI for inspecting and manipulating database entities, enabled by the cli feature.
use microrm::prelude::*;
// start by defining a simple schema
struct PetOwners;
impl microrm::Relation for PetOwners {
type Domain = Owner;
type Codomain = Pet;
const NAME: &'static str = "PetOwners";
const UNIQUE_CODOMAIN: bool = true;
}
#[derive(Entity)]
struct Pet {
#[key]
name: String,
species: String,
owner: microrm::Codomain<PetOwners>,
}
#[derive(Entity)]
struct Owner {
#[key]
name: String,
address: String,
pets: microrm::Domain<PetOwners>,
}
#[derive(Schema)]
struct Schema {
owners: microrm::Table<Owner>,
pets: microrm::Table<Pet>,
}
// make a very simple clap parser via the derive interface:
#[derive(clap::Parser)]
struct Invocation {
#[clap(subcommand)]
cmd: InvocationSubcommand,
}
#[derive(clap::Subcommand)]
enum InvocationSubcommand {
Pet {
#[clap(subcommand)]
cmd: microrm::cli::Autogenerate<PetInterface>,
},
Owner {
#[clap(subcommand)]
cmd: microrm::cli::Autogenerate<OwnerInterface>,
},
}
// now we need to define two helper types: PetInterface and OwnerInterface. these tell the CLI
// generation how to deal with these two entities.
struct PetInterface;
impl microrm::cli::EntityInterface for PetInterface {
type Entity = Pet;
type Error = microrm::Error;
// no custom context needed
type Context = ();
// by default, microrm does not generate a "new" or "create" command, so we need to add it
// here.
type CustomCommand = PetCustom;
// and a handler for it
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 {
PetCustom::Create { name, species } => {
query_ctx.insert(
txn,
Pet {
name,
species,
owner: Default::default(),
},
)?;
},
}
Ok(())
}
}
#[derive(Debug, clap::Subcommand)]
enum PetCustom {
Create { name: String, species: String },
}
struct OwnerInterface;
impl microrm::cli::EntityInterface for OwnerInterface {
type Entity = Owner;
type Error = microrm::Error;
// no custom context needed
type Context = ();
// as before, add a 'create' command
type CustomCommand = OwnerCustom;
// and again, a handler for it...
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 {
OwnerCustom::Create { name, address } => {
query_ctx.insert(
txn,
Owner {
name,
address,
pets: Default::default(),
},
)?;
},
}
Ok(())
}
}
#[derive(Debug, clap::Subcommand)]
enum OwnerCustom {
Create { name: String, address: String },
}
fn main() -> microrm::DBResult<()> {
use clap::Parser;
let inv = Invocation::parse();
// open the database
let (pool, schema) = microrm::ConnectionPool::open::<Schema>("simple_cli.db")?;
let mut txn = pool.start()?;
match inv.cmd {
InvocationSubcommand::Pet { cmd } => cmd.perform(&(), &mut txn, &schema.pets)?,
InvocationSubcommand::Owner { cmd } => cmd.perform(&(), &mut txn, &schema.owners)?,
}
txn.commit()?;
Ok(())
}
/* annotated interaction transcript:
$ alias simple_cli='cargo run -qF cli --example simple_cli'
Create some pets, who must have unique names:
$ simple_cli pet create louie cat
$ simple_cli pet create louie dog
Error: ConstraintViolation("UNIQUE constraint failed: pet.name")
$ simple_cli pet create archibald dog
Create an owner:
$ simple_cli owner create alison "123 Some Street"
Attach louie to alison:
$ simple_cli owner attach alison pets louie
$ simple_cli owner inspect alison
Owner {
name: "alison",
address: "123 Some Street",
}
pets: (1)
[# 1]: Pet { name: "louie", species: "cat" }
Verify that the connection goes both ways:
$ simple_cli pet inspect louie
Pet {
name: "louie",
species: "cat",
}
owner: (1)
[# 1]: Owner { name: "alison", address: "123 Some Street" }
Add a second owner and try to give them louie as well:
$ simple_cli owner create barbara "234 Some Street"
$ simple_cli owner attach barbara pets louie
Error: ConstraintViolation("UNIQUE constraint failed: owner_pet_relation_PetOwners.range")
But we can, of course, give barbara a different pet:
$ simple_cli owner attach barbara pets archibald
*/