/* * This file is a part of morm * and is distributed under the 2-Clause BSD licence. * Author: Tomasz Sowa */ /* * Copyright (c) 2018, 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; log_queries = false; } PostgreSQLConnector::~PostgreSQLConnector() { close(); } void PostgreSQLConnector::close() { if( pg_conn ) { clear_all_query_results(); PQfinish(pg_conn); pg_conn = nullptr; } } void PostgreSQLConnector::clear_all_query_results() { while( !query_results.empty() ) { clear_last_query_result(); } } void PostgreSQLConnector::clear_last_query_result() { if( !query_results.empty() ) { QueryResult & res = query_results.back(); if( res.result ) { PQclear(res.result); } res.result = nullptr; res.result_rows = 0; res.status = PGRES_EMPTY_QUERY; res.cur_row = 0; query_results.pop_back(); } } // to nie tylko dla selectow moze byc uzywane size_t PostgreSQLConnector::last_select_size() { if( !query_results.empty() ) { return query_results.back().result_rows; } return 0; } ExecStatusType PostgreSQLConnector::last_query_status() { if( !query_results.empty() ) { return query_results.back().status; } return PGRES_EMPTY_QUERY; } bool PostgreSQLConnector::is_last_result(ExecStatusType t) { if( !query_results.empty() ) { QueryResult & res = query_results.back(); return (res.result && PQresultStatus(res.result) == t); } return false; } void PostgreSQLConnector::allocate_default_expression() { deallocate_expression(); db_expression = new PostgreSQLExpression(); expression_allocated = true; } void PostgreSQLConnector::set_log_queries(bool log_queries) { this->log_queries = log_queries; } bool PostgreSQLConnector::query(const char * query_str) { // if( pg_conn ) { // if( log_queries ) // { // log << log1 << "Db: executing query: " << q << logend; // } query_results.push_back(QueryResult()); QueryResult & last_res = query_results.back(); last_res.result = PQexec(pg_conn, query_str); if( !last_res.result ) { if( PQstatus(pg_conn) != CONNECTION_OK ) { assert_connection(); last_res.result = PQexec(pg_conn, query_str); } } if( last_res.result ) { last_res.status = PQresultStatus(last_res.result); last_res.result_rows = static_cast(PQntuples(last_res.result)); } else { // log << log1 << "Db: Problem with this query: \"" << q << '\"' << logend; // log << log1 << "Db: " << PQerrorMessage(pg_conn) << logend; } } return (!query_results.empty() && query_results.back().result != nullptr); } const char * PostgreSQLConnector::query_last_sequence(const wchar_t * sequence_table_name) { allocate_default_expression_if_needed(); if( db_expression ) { stream.clear(); stream << "select currval(E'"; db_expression->esc(sequence_table_name, stream); stream << "');"; if( query_select(stream) ) { if( last_select_size() == 1 ) { return get_value_from_result(0, 0); } else { //log << log1 << "Db: error (currval) for table: " << table << ", " << PQerrorMessage(db_conn->GetPgConn()) << logend; } } } return nullptr; } bool PostgreSQLConnector::query(const PT::TextStream & stream) { stream.to_string(query_str); return query(query_str.c_str()); } bool PostgreSQLConnector::query(const std::string & query_str) { return query(query_str.c_str()); } bool PostgreSQLConnector::query_select(const char * query_str) { return (query(query_str) && last_query_status() == PGRES_TUPLES_OK); } bool PostgreSQLConnector::query_update(const char * query_str) { return (query(query_str) && last_query_status() == PGRES_COMMAND_OK); } bool PostgreSQLConnector::query_insert(const char * query_str) { return (query(query_str) && last_query_status() == PGRES_COMMAND_OK); } bool PostgreSQLConnector::query_select(const PT::TextStream & stream) { stream.to_string(query_str); return query_select(query_str.c_str()); } bool PostgreSQLConnector::query_update(const PT::TextStream & stream) { stream.to_string(query_str); return query_update(query_str.c_str()); } bool PostgreSQLConnector::query_insert(const PT::TextStream & stream) { stream.to_string(query_str); return query_insert(query_str.c_str()); } //int PostgreSQLConnector::Rows(PGresult * r) //{ // // PQntuples - Returns the number of rows (tuples) in the query result. Because it returns // // an integer result, large result sets might overflow the return value on 32-bit operating systems. // return PQntuples(r); //} //int PostgreSQLConnector::Cols(PGresult * r) //{ // // PQnfields - Returns the number of columns (fields) in each row of the query result. // return PQnfields(r); //} //long PostgreSQLConnector::AffectedRows(PGresult * r) //{ // // PQcmdTuples - This function returns a string containing the number of rows affected by the SQL // // statement that generated the PGresult. This function can only be used following the execution // // of an INSERT, UPDATE, DELETE, MOVE, FETCH, or COPY statement, or [...] // char * rows_str = PQcmdTuples(r); // can be an empty string // long rows = 0; // // if( rows_str ) // { // rows = strtol(rows_str, 0, 10); // // strtol - If an overflow or underflow occurs, errno is set to ERANGE // // and the function return value is clamped according to the following table: // // Function underflow overflow // // strtol() LONG_MIN LONG_MAX // // if( rows < 0 ) // rows = 0; // } // //return rows; //} void PostgreSQLConnector::set_current_row_at_beginning() { if( !query_results.empty() ) { query_results.back().cur_row = 0; } } void PostgreSQLConnector::advance_current_row() { if( !query_results.empty() ) { query_results.back().cur_row += 1; } } int PostgreSQLConnector::get_column_index(const char * column_name) { int col_index = -1; if( !query_results.empty() ) { QueryResult & res = query_results.back(); if( res.result ) { col_index = PQfnumber(res.result, column_name); // returns -1 if there is no such a column } } return col_index; } int PostgreSQLConnector::get_column_index(const wchar_t * column_name) { PT::WideToUTF8(column_name, temp_column_name); return get_column_index(temp_column_name.c_str()); } //const char * PostgreSQLConnector::get_field_string_value(const wchar_t * field_name) const char * PostgreSQLConnector::get_field_string_value(const char * column_name) { const char * value_str = nullptr; if( !query_results.empty() ) { QueryResult & res = query_results.back(); if( res.result ) { int col_index = PQfnumber(res.result, column_name); if( col_index != -1 ) { if( res.cur_row < res.result_rows ) { value_str = PQgetvalue(res.result, res.cur_row, col_index); } } } } return value_str; } const char * PostgreSQLConnector::get_field_string_value(const wchar_t * column_name) { PT::WideToUTF8(column_name, temp_column_name); return get_field_string_value(temp_column_name.c_str()); } const char * PostgreSQLConnector::get_value_from_result(int row, int col) { const char * value_str = nullptr; if( !query_results.empty() ) { QueryResult & res = query_results.back(); if( res.result ) { value_str = PQgetvalue(res.result, row, col); // can return a null pointer if there is no such an item in the last result } } return value_str; } int PostgreSQLConnector::get_value_length(int row, int col) { int len = 0; if( !query_results.empty() ) { QueryResult & res = query_results.back(); if( res.result ) { len = PQgetlength(res.result, row, col); } } return len; } //void PostgreSQLConnector::get_value_bin(int row, int col, std::string & result, bool clear_string) //{ // if( clear_string ) // result.clear(); // // const char * raw_result = get_value(row, col); // // if( raw_result ) // { // int len = PQgetlength(last_result, row, col); // // if( len > 0 ) // { // unescape_bin(raw_result, len, result); // } // } //} //bool PostgreSQLConnector::AssertValueSpace(PGresult * r, int row, int col, PT::Space & space) //{ // const char * res = AssertValue(r, row, col); // // conf_parser.SetSpace(space); // space.Clear(); // // PT::SpaceParser::Status status = conf_parser.ParseString(res); // // if( status != PT::SpaceParser::ok ) // { // log << log1 << "Db: a problem with parsing a PT::Space"; // // if( status == PT::SpaceParser::syntax_error ) // log << ", syntax error at line: " << conf_parser.line; // // log << logend; // // space.Clear(); // return false; // } // //return true; //} bool PostgreSQLConnector::is_null(int row, int col) { bool is_null = false; if( !query_results.empty() ) { QueryResult & res = query_results.back(); if( res.result ) { is_null = (PQgetisnull(res.result, row, col) == 1); } } return is_null; } //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 << "Db: 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 << log2 << "Db: connection to the database works fine" << logend; //log << log3 << "Db: connection socket: " << PQsocket(pg_conn) << logend; //std::cout << "Db: connection to the database works fine" << std::endl; //std::cout << "Db: connection socket: " << PQsocket(pg_conn) << std::endl; } } void PostgreSQLConnector::wait_for_connection() { if( !pg_conn || PQstatus(pg_conn) != CONNECTION_OK ) { //log << log3 << "Db: waiting for the db to be ready...." << logend << logsave; //std::cout << "Db: waiting for the db to be ready...." << std::endl; while( !assert_connection(false) ) { sleep(5); } //LogConnectionSocket(); } } // 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 << log2 << "Db: connection to the database is lost, trying to recover" << logend; //std::cout << "Db: connection to the database is lost, trying to recover" << std::endl; } was_connection = false; PQreset(pg_conn); } if( pg_conn && PQstatus(pg_conn) == CONNECTION_OK ) { if( !was_connection ) { // if( put_log ) // LogConnectionSocket(); set_db_parameters(); } return true; } else { if( put_log ) { //log << log1 << "Db: connection to db server cannot be established" << logend; //std::cout << "Db: connection to db server cannot be established" << std::endl; } // 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( PQsetClientEncoding(pg_conn, "UTF8") == -1 ) { //log << log1 << "Db: Can't set the proper client encoding" << logend; //std::cout << "Db: Can't set the proper client encoding" << std::endl; } } }