df_st_db 0.3.0-development-2

Database store and query implementation for the DF Storyteller project.
Documentation
use crate::db_object::{DBObject, MatchBy, OrderTypes};
use crate::df_world::{DBDFWorld, Site, StructureCopiedArtifactID, StructureInhabitantHFID};
use crate::schema::sites_structures;
use crate::DbConnection;
use anyhow::Error;
use df_st_core::fillable::{Fillable, Filler};
use df_st_core::item_count::ItemCount;
use df_st_derive::Fillable;
use diesel::expression_methods::ExpressionMethods;
use diesel::prelude::*;
use diesel::query_dsl::RunQueryDsl;
use diesel::Queryable;
use std::collections::HashMap;

#[derive(
    Clone, Debug, AsChangeset, Identifiable, Associations, Queryable, Insertable, Fillable, Default,
)]
#[table_name = "sites_structures"]
#[primary_key(site_id, local_id)]
#[belongs_to(Site)]
// #[belongs_to(Coordinate)]
pub struct Structure {
    pub site_id: i32,
    pub local_id: i32,
    pub world_id: i32,
    pub type_: Option<String>,
    pub subtype: Option<String>,
    pub name: Option<String>,
    pub name2: Option<String>,
    pub entity_id: Option<i32>,
    pub worship_hf_id: Option<i32>,
    // temple
    pub deity_type: Option<i32>,
    pub deity_id: Option<i32>,
    pub religion_id: Option<i32>,
    // dungeon
    pub dungeon_type: Option<i32>,
}

impl Structure {
    pub fn new() -> Self {
        Self::default()
    }
}

impl DBObject<df_st_core::Structure, Structure> for Structure {
    fn add_missing_data_advanced(core_world: &df_st_core::DFWorld, world: &mut DBDFWorld) {
        for site in core_world.sites.values() {
            for structure in &site.structures {
                let mut db_structure = Structure::new();
                db_structure.add_missing_data(&structure);
                db_structure.site_id = site.id;
                world.structures.push(db_structure);

                // Add copied_artifact_ids list
                for copied_artifact_id in &structure.copied_artifact_ids {
                    world
                        .structure_copied_artifact_ids
                        .push(StructureCopiedArtifactID {
                            site_id: site.id,
                            structure_local_id: structure.local_id,
                            copied_artifact_id: *copied_artifact_id,
                            ..Default::default()
                        });
                }

                // Add inhabitant_hf_ids list
                for inhabitant_hf_id in &structure.inhabitant_hf_ids {
                    world
                        .structure_inhabitant_hf_ids
                        .push(StructureInhabitantHFID {
                            site_id: site.id,
                            structure_local_id: structure.local_id,
                            inhabitant_hf_id: *inhabitant_hf_id,
                            ..Default::default()
                        });
                }
            }
        }
    }

    #[cfg(feature = "postgres")]
    fn insert_into_db(conn: &DbConnection, sites_structures: &[Structure]) {
        use diesel::pg::upsert::excluded;
        diesel::insert_into(sites_structures::table)
            .values(sites_structures)
            .on_conflict((
                sites_structures::site_id,
                sites_structures::local_id,
                sites_structures::world_id,
            ))
            .do_update()
            .set((
                sites_structures::type_.eq(excluded(sites_structures::type_)),
                sites_structures::subtype.eq(excluded(sites_structures::subtype)),
                sites_structures::name.eq(excluded(sites_structures::name)),
                sites_structures::name2.eq(excluded(sites_structures::name2)),
                sites_structures::entity_id.eq(excluded(sites_structures::entity_id)),
                sites_structures::worship_hf_id.eq(excluded(sites_structures::worship_hf_id)),
                sites_structures::deity_type.eq(excluded(sites_structures::deity_type)),
                sites_structures::deity_id.eq(excluded(sites_structures::deity_id)),
                sites_structures::religion_id.eq(excluded(sites_structures::religion_id)),
                sites_structures::dungeon_type.eq(excluded(sites_structures::dungeon_type)),
            ))
            .execute(conn)
            .expect("Error saving Structure");
    }

    #[cfg(not(feature = "postgres"))]
    fn insert_into_db(conn: &DbConnection, sites_structures: &[Structure]) {
        diesel::insert_into(sites_structures::table)
            .values(sites_structures)
            .execute(conn)
            .expect("Error saving Structure");
    }

    fn find_db_list(
        conn: &DbConnection,
        id_filter: HashMap<String, i32>,
        _string_filter: HashMap<String, String>,
        offset: i64,
        limit: i64,
        order: Option<OrderTypes>,
        order_by: Option<String>,
        _id_list: Option<Vec<i32>>,
    ) -> Result<Vec<Structure>, Error> {
        use crate::schema::sites_structures::dsl::*;
        let (order_by, asc) = Self::get_order(order, order_by);
        let query = sites_structures.limit(limit).offset(offset);
        let query = query.filter(world_id.eq(id_filter.get("world_id").unwrap_or(&0)));
        if id_filter.get("site_id").is_some() {
            let query = query.filter(site_id.eq(id_filter.get("site_id").unwrap_or(&0)));
            optional_filter! {
                query, id_filter,
                [
                    "local_id" => local_id,
                ],
                {Ok(order_by!{
                    order_by, asc, query, conn,
                    "site_id" => site_id,
                    "local_id" => local_id,
                    "type" => type_,
                    "name" => name,
                    "name2" => name2,
                    "entity_id" => entity_id,
                    "worship_hf_id" => worship_hf_id,
                    "deity_type" => deity_type,
                    "deity_id" => deity_id,
                    "religion_id" => religion_id,
                    "dungeon_type" => dungeon_type,
                })},
            }
        } else {
            optional_filter! {
                query, id_filter,
                [
                    "local_id" => local_id,
                ],
                {Ok(order_by!{
                    order_by, asc, query, conn,
                    "site_id" => site_id,
                    "local_id" => local_id,
                    "type" => type_,
                    "name" => name,
                    "name2" => name2,
                    "entity_id" => entity_id,
                    "worship_hf_id" => worship_hf_id,
                    "deity_type" => deity_type,
                    "deity_id" => deity_id,
                    "religion_id" => religion_id,
                    "dungeon_type" => dungeon_type,
                })},
            }
        }
    }

    fn find_db_item(
        conn: &DbConnection,
        id_filter: HashMap<String, i32>,
    ) -> Result<Option<Structure>, Error> {
        use crate::schema::sites_structures::dsl::*;
        let query = sites_structures;
        let query = query.filter(world_id.eq(id_filter.get("world_id").unwrap_or(&0)));
        let query = query.filter(site_id.eq(id_filter.get("site_id").unwrap_or(&0)));
        let query = query.filter(local_id.eq(id_filter.get("local_id").unwrap_or(&0)));
        Ok(query.first::<Structure>(conn).optional()?)
    }

    fn match_field_by(_match_by: MatchBy) -> Vec<&'static str> {
        vec![
            "site_id",
            "local_id",
            "type",
            "name",
            "name2",
            "entity_id",
            "worship_hf_id",
            "deity_type",
            "deity_id",
            "religion_id",
            "dungeon_type",
        ]
    }

    fn add_nested_items(
        conn: &DbConnection,
        db_list: &[Structure],
        _core_list: Vec<df_st_core::Structure>,
    ) -> Result<Vec<df_st_core::Structure>, Error> {
        use crate::schema::structure_copied_artifact_ids;
        use crate::schema::structure_inhabitant_hf_ids;
        let world_id = match db_list.first() {
            Some(x) => x.world_id,
            None => 0,
        };

        let site_ids: Vec<i32> = db_list.iter().map(|structure| structure.site_id).collect();

        // Add StructureCopiedArtifactID (For each Structure)
        let structure_copied_artifact_list = structure_copied_artifact_ids::table
            .filter(structure_copied_artifact_ids::site_id.eq_any(&site_ids))
            .filter(structure_copied_artifact_ids::world_id.eq(world_id))
            .load::<StructureCopiedArtifactID>(conn)?;

        // Add StructureInhabitantHFID (For each Structure)
        let structure_inhabitant_list = structure_inhabitant_hf_ids::table
            .filter(structure_inhabitant_hf_ids::site_id.eq_any(&site_ids))
            .filter(structure_inhabitant_hf_ids::world_id.eq(world_id))
            .load::<StructureInhabitantHFID>(conn)?;

        // Merge all
        let mut core_list: Vec<df_st_core::Structure> = Vec::new();
        for structure in db_list.iter() {
            let mut core_item = df_st_core::Structure::default();
            core_item.add_missing_data(structure);

            // add StructureCopiedArtifactID
            for copied_artifact_ids in &structure_copied_artifact_list {
                // `structure_local_id` refers to the `local_id`. This is correct.
                #[allow(clippy::suspicious_operation_groupings)]
                if copied_artifact_ids.site_id == core_item.site_id
                    && copied_artifact_ids.structure_local_id == core_item.local_id
                {
                    core_item
                        .copied_artifact_ids
                        .push(copied_artifact_ids.copied_artifact_id);
                }
            }
            // add StructureInhabitantHFID
            for structure_inhabitant_hf_ids in &structure_inhabitant_list {
                // `structure_local_id` refers to the `local_id`. This is correct.
                #[allow(clippy::suspicious_operation_groupings)]
                if structure_inhabitant_hf_ids.site_id == core_item.site_id
                    && structure_inhabitant_hf_ids.structure_local_id == core_item.local_id
                {
                    core_item
                        .inhabitant_hf_ids
                        .push(structure_inhabitant_hf_ids.inhabitant_hf_id);
                }
            }
            core_list.push(core_item);
        }
        Ok(core_list)
    }

    fn get_count_from_db(
        conn: &DbConnection,
        id_filter: HashMap<String, i32>,
        _string_filter: HashMap<String, String>,
        offset: u32,
        limit: u32,
        group_by_opt: Option<String>,
        _id_list: Option<Vec<i32>>,
    ) -> Result<Vec<ItemCount>, Error> {
        use crate::schema::sites_structures::dsl::*;
        let query = sites_structures.limit(limit as i64).offset(offset as i64);
        let query = query.filter(world_id.eq(id_filter.get("world_id").unwrap_or(&0)));
        if id_filter.get("site_id").is_some() {
            let query = query.filter(site_id.eq(id_filter.get("site_id").unwrap_or(&0)));
            optional_filter! {
                query, id_filter,
                [
                    "local_id" => local_id,
                ],
                {group_by!{
                    group_by_opt, query, conn,
                    "site_id" => {site_id: i32},
                    "local_id" => {local_id: i32},
                    "type" => {type_: Option<String>},
                    "subtype" => {subtype: Option<String>},
                    "name" => {name: Option<String>},
                    "name2" => {name2: Option<String>},
                    "entity_id" => {entity_id: Option<i32>},
                    "worship_hf_id" => {worship_hf_id: Option<i32>},
                    "deity_type" => {deity_type: Option<i32>},
                    "deity_id" => {deity_id: Option<i32>},
                    "religion_id" => {religion_id: Option<i32>},
                    "dungeon_type" => {dungeon_type: Option<i32>},
                };},
            };
        } else {
            optional_filter! {
                query, id_filter,
                [
                    "local_id" => local_id,
                ],
                {group_by!{
                    group_by_opt, query, conn,
                    "site_id" => {site_id: i32},
                    "local_id" => {local_id: i32},
                    "type" => {type_: Option<String>},
                    "subtype" => {subtype: Option<String>},
                    "name" => {name: Option<String>},
                    "name2" => {name2: Option<String>},
                    "entity_id" => {entity_id: Option<i32>},
                    "worship_hf_id" => {worship_hf_id: Option<i32>},
                    "deity_type" => {deity_type: Option<i32>},
                    "deity_id" => {deity_id: Option<i32>},
                    "religion_id" => {religion_id: Option<i32>},
                    "dungeon_type" => {dungeon_type: Option<i32>},
                };},
            };
        }
    }
}

/// From Core to DB
impl Filler<Structure, df_st_core::Structure> for Structure {
    fn add_missing_data(&mut self, source: &df_st_core::Structure) {
        self.site_id.add_missing_data(&source.site_id);
        self.local_id.add_missing_data(&source.local_id);
        self.type_.add_missing_data(&source.type_);
        self.subtype.add_missing_data(&source.subtype);
        self.name.add_missing_data(&source.name);
        self.name2.add_missing_data(&source.name2);
        self.entity_id.add_missing_data(&source.entity_id);
        self.worship_hf_id.add_missing_data(&source.worship_hf_id);
        self.deity_type.add_missing_data(&source.deity_type);
        self.deity_id.add_missing_data(&source.deity_id);
        self.religion_id.add_missing_data(&source.religion_id);
        self.dungeon_type.add_missing_data(&source.dungeon_type);
    }
}

/// From DB to Core
impl Filler<df_st_core::Structure, Structure> for df_st_core::Structure {
    fn add_missing_data(&mut self, source: &Structure) {
        self.site_id.add_missing_data(&source.site_id);
        self.local_id.add_missing_data(&source.local_id);
        self.type_.add_missing_data(&source.type_);
        self.subtype.add_missing_data(&source.subtype);
        self.name.add_missing_data(&source.name);
        self.name2.add_missing_data(&source.name2);
        self.entity_id.add_missing_data(&source.entity_id);
        self.worship_hf_id.add_missing_data(&source.worship_hf_id);
        self.deity_type.add_missing_data(&source.deity_type);
        self.deity_id.add_missing_data(&source.deity_id);
        self.religion_id.add_missing_data(&source.religion_id);
        self.dungeon_type.add_missing_data(&source.dungeon_type);
    }
}

impl PartialEq<df_st_core::Structure> for Structure {
    fn eq(&self, other: &df_st_core::Structure) -> bool {
        self.site_id == other.site_id && self.local_id == other.local_id
    }
}

impl PartialEq<Structure> for df_st_core::Structure {
    fn eq(&self, other: &Structure) -> bool {
        self.site_id == other.site_id && self.local_id == other.local_id
    }
}

impl PartialEq<Structure> for Structure {
    fn eq(&self, other: &Self) -> bool {
        self.site_id == other.site_id && self.local_id == other.local_id
    }
}