/* * This file is a part of morm * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2018-2019, 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); if( !psql_result->psql_result ) { if( PQstatus(pg_conn) != CONNECTION_OK ) { assert_connection(); 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 ) { const char * err_msg = PQerrorMessage(pg_conn); if( err_msg ) { PT::UTF8ToWide(err_msg, psql_result->error_msg); } if( log ) { (*log) << PT::Log::log1 << "Morm: Problem with this query: \"" << query_str << '\"' << PT::Log::logend; if( err_msg ) (*log) << PT::Log::log1 << "Morm: " << err_msg << PT::Log::logend; } } 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 ) { (*log) << PT::Log::log1 << "Morm: error (currval) for table: " << sequence_table_name << ", " << PQerrorMessage(pg_conn) << PT::Log::logend; } } } return nullptr; } bool PostgreSQLConnector::query(const PT::TextStream & stream, QueryResult & query_result) { stream.to_string(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_select(const char * query_str, QueryResult & query_result) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); bool result = false; if( psql_result ) { result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_TUPLES_OK); psql_result->status = result; } return result; } bool PostgreSQLConnector::query_update(const char * query_str, QueryResult & query_result) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); bool result = false; if( psql_result ) { result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_COMMAND_OK); psql_result->status = result; } return result; } bool PostgreSQLConnector::query_insert(const char * query_str, QueryResult & query_result) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); bool result = false; if( psql_result ) { result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_COMMAND_OK); psql_result->status = result; } return result; } bool PostgreSQLConnector::query_remove(const char * query_str, QueryResult & query_result) { PostgreSQLQueryResult * psql_result = dynamic_cast(&query_result); bool result = false; if( psql_result ) { result = (do_query(query_str, psql_result) && psql_result->psql_status == PGRES_COMMAND_OK); psql_result->status = result; } return result; } bool PostgreSQLConnector::query_select(const PT::TextStream & stream, QueryResult & query_result) { stream.to_string(query_str); return query_select(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_update(const PT::TextStream & stream, QueryResult & query_result) { stream.to_string(query_str); return query_update(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_insert(const PT::TextStream & stream, QueryResult & query_result) { stream.to_string(query_str); return query_insert(query_str.c_str(), query_result); } bool PostgreSQLConnector::query_remove(const PT::TextStream & stream, QueryResult & query_result) { stream.to_string(query_str); return query_remove(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='0' && c<='7'; } // moves 'i' at least once // return -1 if there is en error int PostgreSQLConnector::UnescapeBin(const char * str, size_t & i, size_t len) { if( str[i] != '\\' ) return str[i++]; i += 1; if( i >= len ) return -1; if( str[i] == '\\' ) return str[i++]; if( i+2 >= len ) { i = len; return -1; } if( !IsCorrectOctalDigit(str[i]) || !IsCorrectOctalDigit(str[i+1]) || !IsCorrectOctalDigit(str[i+2]) ) { i += 3; return -1; } int c = 8*8*CharToInt(str[i]) + 8*CharToInt(str[i+1]) + CharToInt(str[i+2]); i += 3; if( c<0 || c>255 ) return -1; return c; } void PostgreSQLConnector::UnescapeBin(const char * str, size_t len, std::string & out, bool clear_out) { int c; size_t i = 0; if( clear_out ) out.clear(); while( i < len ) { c = UnescapeBin(str, i, len); if( c != -1 ) out += c; } } */ /* converting from a bytea the new way (hex format) */ //char PostgreSQLConnector::UnescapeBinHexToDigit(char hex) //{ // if( hex>='0' && hex<='9' ) // return hex - '0'; // // if( hex>='a' && hex<='z' ) // return hex - 'a' + 10; // // if( hex>='A' && hex<='Z' ) // return hex - 'A' + 10; // //return 0; //} // // //void PostgreSQLConnector::UnescapeBin(const char * str, size_t len, std::string & out, bool clear_out) //{ // if( clear_out ) // out.clear(); // // if( len < 2 || str[0]!='\\' || str[1]!='x' ) // { // log << log1 << "Morm: unsupported binary format (skipping)" << logend; // return; // } // // for(size_t i=2 ; i + 1 < len ; i+=2 ) // { // int c1 = UnescapeBinHexToDigit(str[i]); // int c2 = UnescapeBinHexToDigit(str[i+1]); // // out += ((c1 << 4) | c2); // } //} void PostgreSQLConnector::set_conn_param(const std::wstring & database_name, const std::wstring & user, const std::wstring & pass) { db_database = database_name; db_user = user; db_pass = pass; } void PostgreSQLConnector::overwrite(PT::TextStream & stream) { PT::TextStream::iterator i = stream.begin(); for( ; i != stream.end() ; ++i) { *i = 0; } stream.clear(); } void PostgreSQLConnector::connect() { // IMPROVEME // what about if reconnecting is made in the midle of queries? // e.g. in after_select? (the whole stack query_results will be cleared) close(); allocate_default_expression_if_needed(); if( db_expression ) { stream.clear(); stream << "dbname='"; db_expression->esc(db_database, stream); stream << "' user='"; db_expression->esc(db_user, stream); stream << "' password='"; db_expression->esc(db_pass, stream); stream << "'"; std::string str; stream.to_string(str); pg_conn = PQconnectdb(str.c_str()); overwrite(stream); } // warning! pg_conn can be not null but there cannnot be a connection established // use PQstatus(pg_conn) to check whether the connection works fine } // // // void PostgreSQLConnector::log_connection_socket() { if( pg_conn && log ) { (*log) << PT::Log::log2 << "Morm: connection to the database works fine" << PT::Log::logend; (*log) << PT::Log::log3 << "Morm: connection socket: " << PQsocket(pg_conn) << PT::Log::logend; (*log) << PT::Log::logsave; } } void PostgreSQLConnector::wait_for_connection() { 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( !assert_connection(false) ) { sleep(5); } log_connection_socket(); } } // IMPROVE ME what about the exception now? bool PostgreSQLConnector::assert_connection(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; } } } } }