mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-29 05:07:23 +01:00
Refactor db schema and use Diesel
This commit is contained in:
parent
2f6676ba3b
commit
220821a0e0
14 changed files with 1310 additions and 599 deletions
87
src/db/mod.rs
Normal file
87
src/db/mod.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
pub mod models;
|
||||
pub mod schema;
|
||||
pub mod tables;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use diesel::{
|
||||
backend::Backend,
|
||||
deserialize::{self, FromSql, FromSqlRow},
|
||||
expression::AsExpression,
|
||||
prelude::*,
|
||||
serialize::{self, IsNull, Output, ToSql},
|
||||
sql_types::Text,
|
||||
sqlite::Sqlite,
|
||||
};
|
||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// This makes the SQL migration scripts accessible from the code.
|
||||
const MIGRATIONS: EmbeddedMigrations = diesel_migrations::embed_migrations!();
|
||||
|
||||
/// Connect to a Musicus database and apply any pending migrations.
|
||||
pub fn connect(file_name: &str) -> Result<SqliteConnection> {
|
||||
log::info!("Opening database file '{}'", file_name);
|
||||
let mut connection = SqliteConnection::establish(file_name)?;
|
||||
|
||||
log::info!("Running migrations if necessary");
|
||||
connection
|
||||
.run_pending_migrations(MIGRATIONS)
|
||||
.map_err(|e| anyhow!(e))?;
|
||||
|
||||
// Enable after running migrations to simplify changes in schema.
|
||||
diesel::sql_query("PRAGMA foreign_keys = ON").execute(&mut connection)?;
|
||||
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
/// A single translated string value.
|
||||
#[derive(Serialize, Deserialize, AsExpression, FromSqlRow, Clone, Debug)]
|
||||
#[diesel(sql_type = Text)]
|
||||
pub struct TranslatedString(HashMap<String, String>);
|
||||
|
||||
impl TranslatedString {
|
||||
/// Get the best translation for the user's current locale.
|
||||
///
|
||||
/// This will fall back to the generic variant if no translation exists. If no
|
||||
/// generic translation exists (which is a bug in the data), an empty string is
|
||||
/// returned and a warning is logged.
|
||||
pub fn get(&self) -> &str {
|
||||
// TODO: Get language from locale.
|
||||
let lang = "generic";
|
||||
|
||||
match self.0.get(lang) {
|
||||
Some(s) => s,
|
||||
None => match self.0.get("generic") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
log::warn!("No generic variant for TranslatedString: {:?}", self);
|
||||
""
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB: Backend> FromSql<Text, DB> for TranslatedString
|
||||
where
|
||||
String: FromSql<Text, DB>,
|
||||
{
|
||||
fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
|
||||
let text = String::from_sql(bytes)?;
|
||||
let translated_string = serde_json::from_str(&text)?;
|
||||
Ok(translated_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql<Text, Sqlite> for TranslatedString
|
||||
where
|
||||
String: ToSql<Text, Sqlite>,
|
||||
{
|
||||
fn to_sql(&self, out: &mut Output<Sqlite>) -> serialize::Result {
|
||||
let text = serde_json::to_string(self)?;
|
||||
out.set_value(text);
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
297
src/db/models.rs
Normal file
297
src/db/models.rs
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
//! This module contains higher-level models combining information from
|
||||
//! multiple database tables.
|
||||
|
||||
use std::{fmt::Display, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use diesel::prelude::*;
|
||||
|
||||
use super::{schema::*, tables, TranslatedString};
|
||||
|
||||
// Re-exports for tables that don't need additional information.
|
||||
pub use tables::{Instrument, Person, Role};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Work {
|
||||
pub work_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub parts: Vec<WorkPart>,
|
||||
pub persons: Vec<Person>,
|
||||
pub instruments: Vec<Instrument>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorkPart {
|
||||
pub work_id: String,
|
||||
pub level: u8,
|
||||
pub name: TranslatedString,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ensemble {
|
||||
pub ensemble_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub persons: Vec<(Person, Instrument)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Recording {
|
||||
pub recording_id: String,
|
||||
pub work: Work,
|
||||
pub year: Option<i32>,
|
||||
pub persons: Vec<Performer>,
|
||||
pub ensembles: Vec<Ensemble>,
|
||||
pub tracks: Vec<Track>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Performer {
|
||||
pub person: Person,
|
||||
pub role: Role,
|
||||
pub instrument: Option<Instrument>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Track {
|
||||
pub track_id: String,
|
||||
pub path: String,
|
||||
pub works: Vec<Work>,
|
||||
}
|
||||
|
||||
impl Eq for Person {}
|
||||
impl PartialEq for Person {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.person_id == other.person_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Work {
|
||||
pub fn from_table(data: tables::Work, connection: &mut SqliteConnection) -> Result<Self> {
|
||||
fn visit_children(
|
||||
work_id: &str,
|
||||
level: u8,
|
||||
connection: &mut SqliteConnection,
|
||||
) -> Result<Vec<WorkPart>> {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
let children: Vec<tables::Work> = works::table
|
||||
.filter(works::parent_work_id.eq(work_id))
|
||||
.load(connection)?;
|
||||
|
||||
for child in children {
|
||||
let mut grand_children = visit_children(&child.work_id, level + 1, connection)?;
|
||||
|
||||
parts.push(WorkPart {
|
||||
work_id: child.work_id,
|
||||
level,
|
||||
name: child.name,
|
||||
});
|
||||
|
||||
parts.append(&mut grand_children);
|
||||
}
|
||||
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
let parts = visit_children(&data.work_id, 0, connection)?;
|
||||
|
||||
let persons: Vec<Person> = persons::table
|
||||
.inner_join(work_persons::table)
|
||||
.order(work_persons::sequence_number)
|
||||
.filter(work_persons::work_id.eq(&data.work_id))
|
||||
.select(tables::Person::as_select())
|
||||
.load(connection)?;
|
||||
|
||||
let instruments: Vec<Instrument> = instruments::table
|
||||
.inner_join(work_instruments::table)
|
||||
.order(work_instruments::sequence_number)
|
||||
.filter(work_instruments::work_id.eq(&data.work_id))
|
||||
.select(tables::Instrument::as_select())
|
||||
.load(connection)?;
|
||||
|
||||
Ok(Self {
|
||||
work_id: data.work_id,
|
||||
name: data.name,
|
||||
parts,
|
||||
persons,
|
||||
instruments,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn composers_string(&self) -> String {
|
||||
self.persons
|
||||
.iter()
|
||||
.map(|p| p.name.get().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Work {}
|
||||
impl PartialEq for Work {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.work_id == other.work_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Ensemble {
|
||||
pub fn from_table(data: tables::Ensemble, connection: &mut SqliteConnection) -> Result<Self> {
|
||||
let persons: Vec<(Person, Instrument)> = persons::table
|
||||
.inner_join(ensemble_persons::table.inner_join(instruments::table))
|
||||
.order(ensemble_persons::sequence_number)
|
||||
.filter(ensemble_persons::ensemble_id.eq(&data.ensemble_id))
|
||||
.select((tables::Person::as_select(), tables::Instrument::as_select()))
|
||||
.load(connection)?;
|
||||
|
||||
Ok(Self {
|
||||
ensemble_id: data.ensemble_id,
|
||||
name: data.name,
|
||||
persons,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Ensemble {}
|
||||
impl PartialEq for Ensemble {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.ensemble_id == other.ensemble_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Recording {
|
||||
pub fn from_table(
|
||||
data: tables::Recording,
|
||||
library_path: &str,
|
||||
connection: &mut SqliteConnection,
|
||||
) -> Result<Self> {
|
||||
let work = Work::from_table(
|
||||
works::table
|
||||
.filter(works::work_id.eq(&data.work_id))
|
||||
.first::<tables::Work>(connection)?,
|
||||
connection,
|
||||
)?;
|
||||
|
||||
let persons = recording_persons::table
|
||||
.order(recording_persons::sequence_number)
|
||||
.filter(recording_persons::recording_id.eq(&data.recording_id))
|
||||
.load::<tables::RecordingPerson>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Performer::from_table(r, connection))
|
||||
.collect::<Result<Vec<Performer>>>()?;
|
||||
|
||||
let ensembles: Vec<Ensemble> = ensembles::table
|
||||
.inner_join(recording_ensembles::table)
|
||||
.order(recording_ensembles::sequence_number)
|
||||
.filter(recording_ensembles::recording_id.eq(&data.recording_id))
|
||||
.select(tables::Ensemble::as_select())
|
||||
.load::<tables::Ensemble>(connection)?
|
||||
.into_iter()
|
||||
.map(|e| Ensemble::from_table(e, connection))
|
||||
.collect::<Result<Vec<Ensemble>>>()?;
|
||||
|
||||
let tracks: Vec<Track> = tracks::table
|
||||
.order(tracks::sequence_number)
|
||||
.filter(tracks::recording_id.eq(&data.recording_id))
|
||||
.select(tables::Track::as_select())
|
||||
.load::<tables::Track>(connection)?
|
||||
.into_iter()
|
||||
.map(|t| Track::from_table(t, library_path, connection))
|
||||
.collect::<Result<Vec<Track>>>()?;
|
||||
|
||||
Ok(Self {
|
||||
recording_id: data.recording_id,
|
||||
work,
|
||||
year: data.year,
|
||||
persons,
|
||||
ensembles,
|
||||
tracks,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn performers_string(&self) -> String {
|
||||
let mut performers = self
|
||||
.persons
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
performers.append(
|
||||
&mut self
|
||||
.ensembles
|
||||
.iter()
|
||||
.map(|e| e.name.get().to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
|
||||
performers.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
impl Performer {
|
||||
pub fn from_table(
|
||||
data: tables::RecordingPerson,
|
||||
connection: &mut SqliteConnection,
|
||||
) -> Result<Self> {
|
||||
let person: Person = persons::table
|
||||
.filter(persons::person_id.eq(&data.person_id))
|
||||
.first(connection)?;
|
||||
|
||||
let role: Role = roles::table
|
||||
.filter(roles::role_id.eq(&data.role_id))
|
||||
.first(connection)?;
|
||||
|
||||
let instrument = match &data.instrument_id {
|
||||
Some(instrument_id) => Some(
|
||||
instruments::table
|
||||
.filter(instruments::instrument_id.eq(instrument_id))
|
||||
.first::<Instrument>(connection)?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
person,
|
||||
role,
|
||||
instrument,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Performer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.instrument {
|
||||
Some(instrument) => {
|
||||
format!("{} ({})", self.person.name.get(), instrument.name.get()).fmt(f)
|
||||
}
|
||||
None => self.person.name.get().fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
pub fn from_table(
|
||||
data: tables::Track,
|
||||
library_path: &str,
|
||||
connection: &mut SqliteConnection,
|
||||
) -> Result<Self> {
|
||||
let works: Vec<Work> = works::table
|
||||
.inner_join(track_works::table)
|
||||
.order(track_works::sequence_number)
|
||||
.filter(track_works::track_id.eq(&data.track_id))
|
||||
.select(tables::Work::as_select())
|
||||
.load::<tables::Work>(connection)?
|
||||
.into_iter()
|
||||
.map(|w| Work::from_table(w, connection))
|
||||
.collect::<Result<Vec<Work>>>()?;
|
||||
|
||||
Ok(Self {
|
||||
track_id: data.track_id,
|
||||
path: Path::new(library_path)
|
||||
.join(&data.path)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
works,
|
||||
})
|
||||
}
|
||||
}
|
||||
181
src/db/schema.rs
Normal file
181
src/db/schema.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
ensemble_persons (ensemble_id, person_id, instrument_id) {
|
||||
ensemble_id -> Text,
|
||||
person_id -> Text,
|
||||
instrument_id -> Text,
|
||||
sequence_number -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
ensembles (ensemble_id) {
|
||||
ensemble_id -> Text,
|
||||
name -> Text,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
last_played_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
instruments (instrument_id) {
|
||||
instrument_id -> Text,
|
||||
name -> Text,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
last_played_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
persons (person_id) {
|
||||
person_id -> Text,
|
||||
name -> Text,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
last_played_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
recording_ensembles (recording_id, ensemble_id, role_id) {
|
||||
recording_id -> Text,
|
||||
ensemble_id -> Text,
|
||||
role_id -> Text,
|
||||
sequence_number -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
recording_persons (recording_id, person_id, role_id, instrument_id) {
|
||||
recording_id -> Text,
|
||||
person_id -> Text,
|
||||
role_id -> Text,
|
||||
instrument_id -> Nullable<Text>,
|
||||
sequence_number -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
recordings (recording_id) {
|
||||
recording_id -> Text,
|
||||
work_id -> Text,
|
||||
year -> Nullable<Integer>,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
last_played_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
roles (role_id) {
|
||||
role_id -> Text,
|
||||
name -> Text,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
track_works (track_id, work_id) {
|
||||
track_id -> Text,
|
||||
work_id -> Text,
|
||||
sequence_number -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
tracks (track_id) {
|
||||
track_id -> Text,
|
||||
recording_id -> Text,
|
||||
sequence_number -> Integer,
|
||||
path -> Text,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
last_played_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
work_instruments (work_id, instrument_id) {
|
||||
work_id -> Text,
|
||||
instrument_id -> Text,
|
||||
sequence_number -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
work_persons (work_id, person_id, role_id) {
|
||||
work_id -> Text,
|
||||
person_id -> Text,
|
||||
role_id -> Text,
|
||||
sequence_number -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
work_sections (id) {
|
||||
id -> BigInt,
|
||||
work -> Text,
|
||||
title -> Text,
|
||||
before_index -> BigInt,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
works (work_id) {
|
||||
work_id -> Text,
|
||||
parent_work_id -> Nullable<Text>,
|
||||
sequence_number -> Nullable<Integer>,
|
||||
name -> Text,
|
||||
created_at -> Timestamp,
|
||||
edited_at -> Timestamp,
|
||||
last_used_at -> Timestamp,
|
||||
last_played_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(ensemble_persons -> ensembles (ensemble_id));
|
||||
diesel::joinable!(ensemble_persons -> instruments (instrument_id));
|
||||
diesel::joinable!(ensemble_persons -> persons (person_id));
|
||||
diesel::joinable!(recording_ensembles -> ensembles (ensemble_id));
|
||||
diesel::joinable!(recording_ensembles -> recordings (recording_id));
|
||||
diesel::joinable!(recording_ensembles -> roles (role_id));
|
||||
diesel::joinable!(recording_persons -> instruments (instrument_id));
|
||||
diesel::joinable!(recording_persons -> persons (person_id));
|
||||
diesel::joinable!(recording_persons -> recordings (recording_id));
|
||||
diesel::joinable!(recording_persons -> roles (role_id));
|
||||
diesel::joinable!(recordings -> works (work_id));
|
||||
diesel::joinable!(track_works -> tracks (track_id));
|
||||
diesel::joinable!(track_works -> works (work_id));
|
||||
diesel::joinable!(tracks -> recordings (recording_id));
|
||||
diesel::joinable!(work_instruments -> instruments (instrument_id));
|
||||
diesel::joinable!(work_instruments -> works (work_id));
|
||||
diesel::joinable!(work_persons -> persons (person_id));
|
||||
diesel::joinable!(work_persons -> roles (role_id));
|
||||
diesel::joinable!(work_persons -> works (work_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
ensemble_persons,
|
||||
ensembles,
|
||||
instruments,
|
||||
persons,
|
||||
recording_ensembles,
|
||||
recording_persons,
|
||||
recordings,
|
||||
roles,
|
||||
track_works,
|
||||
tracks,
|
||||
work_instruments,
|
||||
work_persons,
|
||||
work_sections,
|
||||
works,
|
||||
);
|
||||
142
src/db/tables.rs
Normal file
142
src/db/tables.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//! This module contains structs that are one-to-one representations of the
|
||||
//! tables in the database schema.
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::Sqlite;
|
||||
|
||||
use super::{schema::*, TranslatedString};
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Person {
|
||||
pub person_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
pub last_played_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Role {
|
||||
pub role_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Instrument {
|
||||
pub instrument_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
pub last_played_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Work {
|
||||
pub work_id: String,
|
||||
pub parent_work_id: Option<String>,
|
||||
pub sequence_number: Option<i32>,
|
||||
pub name: TranslatedString,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
pub last_played_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct WorkPerson {
|
||||
pub work_id: String,
|
||||
pub person_id: String,
|
||||
pub role_id: String,
|
||||
pub sequence_number: i32,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct WorkInstrument {
|
||||
pub work_id: String,
|
||||
pub instrument_id: String,
|
||||
pub sequence_number: i32,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Ensemble {
|
||||
pub ensemble_id: String,
|
||||
pub name: TranslatedString,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
pub last_played_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct EnsemblePerson {
|
||||
pub ensemble_id: String,
|
||||
pub person_id: String,
|
||||
pub instrument_id: String,
|
||||
pub sequence_number: i32,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Recording {
|
||||
pub recording_id: String,
|
||||
pub work_id: String,
|
||||
pub year: Option<i32>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
pub last_played_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct RecordingPerson {
|
||||
pub recording_id: String,
|
||||
pub person_id: String,
|
||||
pub role_id: String,
|
||||
pub instrument_id: Option<String>,
|
||||
pub sequence_number: i32,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct RecordingEnsemble {
|
||||
pub recording_id: String,
|
||||
pub ensemble_id: String,
|
||||
pub role_id: String,
|
||||
pub sequence_number: i32,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct Track {
|
||||
pub track_id: String,
|
||||
pub recording_id: String,
|
||||
pub sequence_number: i32,
|
||||
pub path: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub edited_at: NaiveDateTime,
|
||||
pub last_used_at: NaiveDateTime,
|
||||
pub last_played_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Queryable, Selectable, Clone, Debug)]
|
||||
#[diesel(check_for_backend(Sqlite))]
|
||||
pub struct TrackWork {
|
||||
pub track_id: String,
|
||||
pub work_id: String,
|
||||
pub sequence_number: i32,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue