/* * This file is a part of morm * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2018-2021, Tomasz Sowa * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include "model.h" namespace morm { Model::Model() { model_connector = nullptr; model_env = nullptr; save_mode = DO_INSERT_ON_SAVE; has_primary_key_set = false; } Model::Model(const Model & m) { model_connector = m.model_connector; save_mode = m.save_mode; model_env = nullptr; has_primary_key_set = m.has_primary_key_set; } Model::~Model() { } void Model::set_save_mode(SaveMode save_mode) { this->save_mode = save_mode; } Model::SaveMode Model::get_save_mode() { return save_mode; } void Model::set_has_primary_key_set(bool has_primary_key) { this->has_primary_key_set = has_primary_key; } bool Model::get_has_primary_key_set() { return this->has_primary_key_set; } void Model::mark_to_delete() { save_mode = DO_DELETE_ON_SAVE; } void Model::mark_to_remove() { save_mode = DO_DELETE_ON_SAVE; } void Model::mark_to_insert() { save_mode = DO_INSERT_ON_SAVE; } void Model::mark_to_update() { save_mode = DO_UPDATE_ON_SAVE; } void Model::table_name(PT::TextStream & stream) { } void Model::set_connector(ModelConnector & connector) { set_connector(&connector); } void Model::set_connector(ModelConnector * connector) { model_connector = connector; } ModelConnector * Model::get_connector() { return model_connector; } bool Model::object_exists() { return save_mode == DO_UPDATE_ON_SAVE; } bool Model::found() { return save_mode == DO_UPDATE_ON_SAVE; } void Model::to_text(PT::TextStream & stream, ModelData * model_data, bool clear_stream, bool dump_mode) { if( clear_stream ) { stream.clear(); } ModelEnv model_env_local; model_env = &model_env_local; model_env->has_primary_key_set = has_primary_key_set; model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_FLAT_STRING; model_env->dump_mode = dump_mode; model_env->model_data = model_data; if( model_connector ) { FlatConnector * flat_connector = model_connector->get_flat_connector(); if( flat_connector ) { try { flat_connector->to_text(stream, *this); } catch(...) { model_env = nullptr; throw; } } } model_env = nullptr; } void Model::to_text(PT::TextStream & stream, ModelData & model_data, bool clear_stream, bool dump_mode) { to_text(stream, &model_data, clear_stream, dump_mode); } void Model::to_text(PT::TextStream & stream, bool clear_stream, bool dump_mode) { to_text(stream, nullptr, clear_stream, dump_mode); } void Model::to_text(std::string & str, ModelData * model_data, bool clear_string, bool dump_mode) { if( model_connector ) { // CHECK ME what if the stream is being used by something other? PT::TextStream * out_stream = model_connector->get_stream(); if( out_stream ) { to_text(*out_stream, model_data, true, dump_mode); out_stream->to_string(str, clear_string); } } } void Model::to_text(std::string & str, ModelData & model_data, bool clear_string, bool dump_mode) { to_text(str, &model_data, clear_string, dump_mode); } void Model::to_text(std::string & str, bool clear_string, bool dump_mode) { to_text(str, nullptr, clear_string, dump_mode); } std::string Model::to_text() { std::string str; to_text(str, false); return str; } std::string Model::to_string() { return to_text(); } void Model::generate_insert_query(PT::TextStream & stream, ModelData * model_data) { ModelEnv model_env_local; model_env = &model_env_local; model_env->has_primary_key_set = has_primary_key_set; model_env->model_data = model_data; model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; if( model_connector ) { DbConnector * db_connector = model_connector->get_db_connector(); if( db_connector ) { try { db_connector->generate_insert_query(stream, *this); } catch(...) { model_env = nullptr; throw; } } } model_env = nullptr; } bool Model::insert(ModelData & model_data, bool insert_whole_tree) { return insert(&model_data, insert_whole_tree); } bool Model::insert(bool insert_whole_tree) { return insert(nullptr, insert_whole_tree); } bool Model::insert(ModelData * model_data, bool insert_whole_tree) { ModelEnv model_env_local; model_env = &model_env_local; model_env->model_data = model_data; bool status = false; try { status = insert_tree(insert_whole_tree); } catch(...) { model_env = nullptr; throw; } model_env = nullptr; return status; } // has ModelEnv set // FIX ME we need to propagage the status from the whole tree, if there is an error somewhere then we should return error from the parent bool Model::insert_tree(bool insert_whole_tree) { bool result = false; has_primary_key_set = false; // the key will be overwritten (the database will create a new key) model_env->has_primary_key_set = false; if( insert_whole_tree ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITH_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_INSERT; map_fields(); } if( model_connector ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; DbConnector * db_connector = model_connector->get_db_connector(); // CHECK ME what if the stream is being used by something other? PT::TextStream * out_stream = model_connector->get_stream(); if( db_connector && out_stream ) { before_insert(); out_stream->clear(); result = db_connector->insert(*out_stream, *this); if( result ) { /* * after_insert() should read the new primary key and set has_primary_key_set flag if the key was read correctly */ after_insert(); model_env->has_primary_key_set = has_primary_key_set; if( has_primary_key_set ) { save_mode = DO_UPDATE_ON_SAVE; set_parent_key_in_childs(); // may it would be better to set it even if we do not have a primary key? set it to zero or something? } else { save_mode = DO_NOTHING_ON_SAVE; } } else { after_insert_failure(); } } } if( insert_whole_tree ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITHOUT_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_INSERT; map_fields(); } return result; } void Model::generate_update_query(PT::TextStream & stream, ModelData * model_data) { ModelEnv model_env_local; model_env = &model_env_local; model_env->has_primary_key_set = has_primary_key_set; model_env->model_data = model_data; model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; if( model_connector ) { DbConnector * db_connector = model_connector->get_db_connector(); if( db_connector ) { db_connector->generate_update_query(stream, *this); } } // what about if an exception was thrown? this pointer will not be null model_env = nullptr; } bool Model::update(ModelData & model_data, bool update_whole_tree) { return update(&model_data, update_whole_tree); } bool Model::update(bool update_whole_tree) { return update(nullptr, update_whole_tree); } bool Model::update(ModelData * model_data, bool update_whole_tree) { ModelEnv model_env_local; model_env = &model_env_local; model_env->model_data = model_data; bool status = false; try { status = update_tree(update_whole_tree); } catch(...) { model_env = nullptr; throw; } model_env = nullptr; return status; } // FIX ME we need to propagage the status from the whole tree, if there is an error somewhere then we should return error from the parent bool Model::update_tree(bool update_whole_tree) { bool result = false; model_env->has_primary_key_set = has_primary_key_set; if( !has_primary_key_set ) { put_to_log(L"Morm: call update but model doesn't have a primary key set"); return result; } if( update_whole_tree ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITH_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_UPDATE; map_fields(); } if( model_connector ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; DbConnector * db_connector = model_connector->get_db_connector(); // CHECK ME what if the stream is being used by something other? PT::TextStream * out_stream = model_connector->get_stream(); if( db_connector && out_stream ) { before_update(); out_stream->clear(); result = db_connector->update(*out_stream, *this); if( result ) after_update(); else after_update_failure(); } } if( update_whole_tree ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITHOUT_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_UPDATE; map_fields(); } return result; } void Model::generate_remove_query(PT::TextStream & stream, ModelData * model_data) { ModelEnv model_env_local; model_env = &model_env_local; model_env->has_primary_key_set = has_primary_key_set; model_env->model_data = model_data; model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; if( model_connector ) { DbConnector * db_connector = model_connector->get_db_connector(); if( db_connector ) { db_connector->generate_remove_query(stream, *this); } } // what about if an exception was thrown? this pointer will not be null model_env = nullptr; } bool Model::remove(ModelData & model_data, bool remove_whole_tree) { return remove(&model_data, remove_whole_tree); } bool Model::remove(bool remove_whole_tree) { return remove(nullptr, remove_whole_tree); } bool Model::remove(ModelData * model_data, bool remove_whole_tree) { ModelEnv model_env_local; model_env = &model_env_local; model_env->model_data = model_data; bool status = false; try { status = remove_tree(remove_whole_tree); } catch(...) { model_env = nullptr; throw; } model_env = nullptr; return status; } // FIX ME we need to propagage the status from the whole tree, if there is an error somewhere then we should return error from the parent bool Model::remove_tree(bool remove_whole_tree) { bool result = false; model_env->has_primary_key_set = has_primary_key_set; if( !has_primary_key_set ) { put_to_log(L"Morm: call remove but model doesn't have a primary key set"); return result; } if( remove_whole_tree ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITHOUT_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_REMOVE; map_fields(); } if( model_connector ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; DbConnector * db_connector = model_connector->get_db_connector(); // CHECK ME what if the stream is being used by something other? PT::TextStream * out_stream = model_connector->get_stream(); if( db_connector && out_stream ) { before_remove(); out_stream->clear(); result = db_connector->remove(*out_stream, *this); if( result ) { save_mode = DO_NOTHING_ON_SAVE; has_primary_key_set = false; model_env->has_primary_key_set = false; after_remove(); } else { after_remove_failure(); } } } if( remove_whole_tree ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITH_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_REMOVE; map_fields(); } return result; } bool Model::save(ModelData & model_data, bool save_whole_tree) { return save(&model_data, save_whole_tree); } bool Model::save(bool save_whole_tree) { return save(nullptr, save_whole_tree); } bool Model::save(ModelData * model_data, bool save_whole_tree) { ModelEnv model_env_local; model_env = &model_env_local; model_env->model_data = model_data; bool status = false; try { status = save_tree(save_whole_tree); } catch(...) { model_env = nullptr; throw; } model_env = nullptr; return status; } // FIX ME we need to propagage the status from the whole tree, if there is an error somewhere then we should return error from the parent bool Model::save_tree(bool save_whole_tree) { bool result = false; model_env->has_primary_key_set = has_primary_key_set; if( save_whole_tree ) { if( save_mode == DO_DELETE_ON_SAVE ) model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITHOUT_FOREIGN_KEY; else model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITH_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_SAVE; map_fields(); } ModelEnv * old_model_env = model_env; // remove, insert or update will set model_env to nullptr switch( save_mode ) { case DO_DELETE_ON_SAVE: result = remove_tree(false); break; case DO_INSERT_ON_SAVE: result = insert_tree(false); break; case DO_UPDATE_ON_SAVE: result = update_tree(false); break; case DO_NOTHING_ON_SAVE: result = true; break; } model_env = old_model_env; if( save_whole_tree ) { if( save_mode == DO_DELETE_ON_SAVE ) model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITH_FOREIGN_KEY; else model_env->model_work_mode = MORM_MODEL_WORK_MODE_ITERATE_THROUGH_CHILDS_WITHOUT_FOREIGN_KEY; model_env->model_work_submode = MORM_MODEL_WORK_SUBMODE_SAVE; map_fields(); } return result; } void Model::generate_select_columns(PT::TextStream & stream) { if( model_connector && model_env ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_GENERATING_DB_SQL; DbConnector * db_connector = model_connector->get_db_connector(); if( db_connector ) { db_connector->generate_select_columns(stream, *this); } } } void Model::map_values_from_query() { if( model_env ) { model_env->model_work_mode = MORM_MODEL_WORK_MODE_READING_VALUE_FROM_DB_RESULTSET; model_env->was_primary_key_read = false; // whether or not there was at least one column with primary_key flag model_env->has_primary_key_set = true; // whether all primary_columns were different than null map_fields(); model_env->model_work_mode = MORM_MODEL_WORK_MODE_NONE; if( model_env->was_primary_key_read && model_env->has_primary_key_set ) { has_primary_key_set = true; save_mode = DO_UPDATE_ON_SAVE; } else { has_primary_key_set = false; save_mode = DO_NOTHING_ON_SAVE; } } } void Model::clear() { ModelEnv model_env_local; model_env = &model_env_local; model_env->model_work_mode = MORM_MODEL_WORK_MODE_CLEARING_VALUE; try { map_fields(); } catch(...) { model_env = nullptr; throw; } model_env = nullptr; save_mode = DO_INSERT_ON_SAVE; has_primary_key_set = false; } void Model::before_select() { } void Model::before_insert() { } void Model::before_update() { } void Model::before_remove() { } void Model::after_select() { } void Model::after_insert() { } void Model::after_update() { } void Model::after_remove() { } void Model::after_select_failure() { } void Model::after_insert_failure() { } void Model::after_update_failure() { } void Model::after_remove_failure() { } int Model::get_work_mode() { if( model_env ) { return model_env->model_work_mode; } else { return MORM_MODEL_WORK_MODE_NONE; } } ModelData * Model::get_model_data() { if( model_env ) { return model_env->model_data; } else { return nullptr; } } bool Model::is_empty_field(const wchar_t * value) { return (!value || *value == '\0'); } bool Model::is_the_same_field(const wchar_t * field1, const wchar_t * field2) { if( is_empty_field(field1) && is_empty_field(field2) ) return true; if( is_empty_field(field1) || is_empty_field(field2) ) return false; bool the_same = false; while( *field1 && *field2 ) { field1 += 1; field2 += 1; } if( *field1 == 0 && *field2 == 0 ) { the_same = true; } return the_same; } void Model::prepare_table_names(bool prepare_table_index) { DbConnector * db_connector = model_connector->get_db_connector(); if( db_connector && model_env ) { DbExpression * db_expression = db_connector->get_expression(); if( db_expression ) { table_name(model_env->table_name); db_expression->prepare_short_table_name(model_env->table_name, model_env->table_name_short); if( prepare_table_index && model_env->finder_helper ) { model_env->table_index = model_env->finder_helper->add_join_table(model_env->table_name_short); } } } } void Model::put_table_name_with_index(PT::TextStream & str) { if( model_env ) { str << model_env->table_name_short; if( model_env->table_index > 1 ) { str << model_env->table_index; } } } void Model::put_to_log(const wchar_t * str) { if( model_connector ) { PT::Log * log = model_connector->get_logger(); if( log ) { (*log) << str << PT::Log::logend; } } } void Model::put_fields_to_log(PT::Log & log, const wchar_t * db_field_name, const wchar_t * flat_field_name) { bool was_db_field_put = false; bool was_flat_field_put = false; if( !is_empty_field(db_field_name) ) { log << "database field name: " << db_field_name; was_db_field_put = true; } if( !is_empty_field(flat_field_name) ) { if( was_db_field_put ) log << ", "; log << "flat field name: " << flat_field_name; was_flat_field_put = true; } if( !was_db_field_put && !was_flat_field_put ) { log << "(both database field name and flat field name are empty)"; } } } // namespace