/* * This file is a part of morm * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2018-2023, 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. * */ // for sleep() #include #include "postgresqlconnector.h" #include "utf8/utf8.h" #include "postgresqlexpression.h" #include "convert/strtoint.h" namespace morm { PostgreSQLConnector::PostgreSQLConnector() { pg_conn = nullptr; } PostgreSQLConnector::~PostgreSQLConnector() { close(); } void PostgreSQLConnector::close() { if( pg_conn ) { PQfinish(pg_conn); pg_conn = nullptr; } } void PostgreSQLConnector::allocate_default_expression() { deallocate_expression(); db_expression = new PostgreSQLExpression(); expression_allocated = true; } QueryResult * PostgreSQLConnector::create_query_result() { return new PostgreSQLQueryResult(); } bool PostgreSQLConnector::do_query(const char * query_str, PostgreSQLQueryResult * psql_result) { if( pg_conn && psql_result ) { psql_result->clear(); if( log_queries && log ) { (*log) << pt::Log::log3 << "Morm: query: " << query_str << pt::Log::logend; } psql_result->psql_result = PQexec(pg_conn, query_str); /* * in older versions of PostgreSQL when there was a connection issue then the psql_result pointer would be null * */ if( !psql_result->psql_result || PQstatus(pg_conn) != CONNECTION_OK ) { psql_result->clear(); assert_connection_is_working(); psql_result->psql_result = PQexec(pg_conn, query_str); } if( psql_result->psql_result ) { psql_result->psql_status = PQresultStatus(psql_result->psql_result); psql_result->result_rows = static_cast(PQntuples(psql_result->psql_result)); psql_result->result_cols = static_cast(PQnfields(psql_result->psql_result)); } if( !psql_result->psql_result || psql_result->psql_status == PGRES_FATAL_ERROR ) { if( log ) { const char * err_msg = PQerrorMessage(pg_conn); (*log) << pt::Log::log1 << "Morm: Problem with this query: \"" << query_str << '\"' << pt::Log::logend; if( err_msg ) { log->put_multiline("Morm: ", err_msg); } } } else { psql_result->status = true; } } return (pg_conn && psql_result && psql_result->psql_result != nullptr && psql_result->status); } bool PostgreSQLConnector::query(const char * query_str, QueryResult & query_result) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); return do_query(query_str, psql_result); } const char * PostgreSQLConnector::query_last_sequence(const wchar_t * sequence_table_name) { allocate_default_expression_if_needed(); if( db_expression ) { PostgreSQLQueryResult psql_result; stream.clear(); stream << "select currval(E'"; db_expression->esc(sequence_table_name, stream); stream << "');"; if( query_select(stream, psql_result) ) { if( psql_result.result_rows == 1 ) { return psql_result.get_value_from_result(0, 0); } else { if( log ) { (*log) << pt::Log::log1 << "Morm: expected only one row in sequence result, has: " << psql_result.result_rows << pt::Log::logend; } } } else { if( pg_conn && log ) { const char * err_msg = PQerrorMessage(pg_conn); (*log) << pt::Log::log1 << "Morm: error (currval) for table: " << sequence_table_name << pt::Log::logend; if( err_msg ) { log->put_multiline("Morm: ", err_msg); } } } } return nullptr; } bool PostgreSQLConnector::query(const pt::TextStream & stream, QueryResult & query_result) { stream.to_str(query_str); return query(query_str.c_str(), query_result); } bool PostgreSQLConnector::query(const std::string & query_str, QueryResult & query_result) { return query(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_command(const char * query_str, QueryResult & query_result, ExecStatusType expected_status) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); bool result = false; if( psql_result ) { result = (do_query(query_str, psql_result) && psql_result->psql_status == expected_status); psql_result->status = result; } return result; } bool PostgreSQLConnector::query_select(const char * query_str, QueryResult & query_result) { return query_command(query_str, query_result, PGRES_TUPLES_OK); } bool PostgreSQLConnector::query_update(const char * query_str, QueryResult & query_result) { return query_command(query_str, query_result, PGRES_COMMAND_OK); } bool PostgreSQLConnector::query_insert(const char * query_str, QueryResult & query_result) { return query_command(query_str, query_result, PGRES_COMMAND_OK); } bool PostgreSQLConnector::query_remove(const char * query_str, QueryResult & query_result) { return query_command(query_str, query_result, PGRES_COMMAND_OK); } bool PostgreSQLConnector::query_declare_cursor(const char * query_str, QueryResult & query_result) { return query_command(query_str, query_result, PGRES_COMMAND_OK); } bool PostgreSQLConnector::query_select(const pt::TextStream & stream, QueryResult & query_result) { stream.to_str(query_str); return query_select(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_update(const pt::TextStream & stream, QueryResult & query_result) { stream.to_str(query_str); return query_update(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_insert(const pt::TextStream & stream, QueryResult & query_result) { stream.to_str(query_str); return query_insert(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_remove(const pt::TextStream & stream, QueryResult & query_result) { stream.to_str(query_str); return query_remove(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_declare_cursor(const pt::TextStream & stream, QueryResult & query_result) { stream.to_str(query_str); return query_declare_cursor(query_str.c_str(), query_result); } //void PostgreSQLConnector::CreateIdList(const std::vector & id_tab, std::wstring & list, bool add_parentheses) //{ //wchar_t buffer[50]; //size_t buffer_len = sizeof(buffer) / sizeof(wchar_t); // // list.clear(); // // if( add_parentheses ) // list += '('; // // for(size_t i=0 ; i 120 ) attempt_delay = 120; if( !pg_conn || PQstatus(pg_conn) != CONNECTION_OK ) { if( log ) { (*log) << pt::Log::log3 << "Morm: waiting for the db to be ready...." << pt::Log::logend << pt::Log::logsave; } while( !attempts_exceeded && !assert_connection_is_working(false) ) { if( attempts_max != 0 ) { attempts += 1; attempts_exceeded = (attempts >= attempts_max); } if( !attempts_exceeded ) { sleep(attempt_delay); } } } if( attempts_exceeded ) { log_no_connection(attempts); } else { log_connection_socket(); } return !attempts_exceeded; } // IMPROVE ME what about the exception now? bool PostgreSQLConnector::assert_connection_is_working(bool put_log) { bool was_connection = true; if( !pg_conn ) { was_connection = false; connect(); } else if( PQstatus(pg_conn) != CONNECTION_OK ) { if( put_log && log ) { (*log) << pt::Log::log2 << "Morm: connection to the database is lost, trying to recover" << pt::Log::logend << pt::Log::logsave; } was_connection = false; PQreset(pg_conn); } if( pg_conn && PQstatus(pg_conn) == CONNECTION_OK ) { if( !was_connection ) { if( put_log ) log_connection_socket(); set_db_parameters(); } return true; } else { if( put_log && log ) { (*log) << pt::Log::log1 << "Morm: connection to db server cannot be established" << pt::Log::logend << pt::Log::logsave; } // if( throw_if_no_connection ) // { // //throw Error(WINIX_ERR_DB_FATAL_ERROR_DURING_CONNECTING); // throw int(10); // } return false; } } void PostgreSQLConnector::set_db_parameters() { if( pg_conn ) { if( PQsetClientEncoding(pg_conn, "UTF8") == -1 ) { if( log ) { (*log) << pt::Log::log1 << "Morm: Can't set the proper client encoding" << pt::Log::logend << pt::Log::logsave; } } } } void PostgreSQLConnector::log_unsupported_bin_format() { if( log ) { (*log) << pt::Log::log1 << "Morm: unsupported binary format (skipping)" << pt::Log::logend; } } void PostgreSQLConnector::unescape_bin_char(const char * str, char & field_value) { if( str[0]!='\\' || str[1]!='x' ) { log_unsupported_bin_format(); } else { DbConnector::unescape_bin_char(str + 2, field_value); } } void PostgreSQLConnector::unescape_bin_char(const char * str, wchar_t & field_value) { if( str[0]!='\\' || str[1]!='x' ) { log_unsupported_bin_format(); } else { DbConnector::unescape_bin_char(str + 2, field_value); } } void PostgreSQLConnector::unescape_bin_string(const char * str, std::string & out) { if( str[0]!='\\' || str[1]!='x' ) { log_unsupported_bin_format(); } else { DbConnector::unescape_bin_string(str + 2, out); } } void PostgreSQLConnector::unescape_bin_string(const char * str, std::wstring & out) { if( str[0]!='\\' || str[1]!='x' ) { log_unsupported_bin_format(); } else { DbConnector::unescape_bin_string(str + 2, out); } } }