/*========================================================================= Program: Visualization Toolkit Module: vtkPostgreSQLQuery.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ /*------------------------------------------------------------------------- Copyright 2008 Sandia Corporation. Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain rights in this software. -------------------------------------------------------------------------*/ #include "vtkPostgreSQLQuery.h" #include "vtkObjectFactory.h" #include "vtkPostgreSQLDatabase.h" #include "vtkStringArray.h" #include "vtkVariant.h" #include "vtkVariantArray.h" #include "vtkPostgreSQLDatabasePrivate.h" #include #include // man, I hope all platforms have this nowadays #include #define BEGIN_TRANSACTION "BEGIN" #define COMMIT_TRANSACTION "COMMIT" #define ROLLBACK_TRANSACTION "ROLLBACK" #define DECLARE_CONVERTER(TargetType) vtkVariant ConvertStringTo##TargetType(bool, const char *); DECLARE_CONVERTER(Boolean); DECLARE_CONVERTER(SignedChar); DECLARE_CONVERTER(UnsignedChar); DECLARE_CONVERTER(SignedShort); DECLARE_CONVERTER(UnsignedShort); DECLARE_CONVERTER(SignedInt); DECLARE_CONVERTER(UnsignedInt); DECLARE_CONVERTER(SignedLong); DECLARE_CONVERTER(UnsignedLong); DECLARE_CONVERTER(Float); DECLARE_CONVERTER(Double); DECLARE_CONVERTER(VtkIdType); DECLARE_CONVERTER(String); DECLARE_CONVERTER(SignedLongLong); DECLARE_CONVERTER(UnsignedLongLong); template void ConvertFromNetworkOrder(T &target, const char *rawBytes) { for (unsigned int i = 0; i < sizeof(T); ++i) { int targetByte = sizeof(T) - (i+1); target |= (rawBytes[i] << (8*targetByte)); } } // ---------------------------------------------------------------------- vtkStandardNewMacro(vtkPostgreSQLQuery); // ---------------------------------------------------------------------- class vtkPostgreSQLQueryPrivate { public: vtkPostgreSQLQueryPrivate() { this->QueryResults = nullptr; this->CurrentRow = -1; } ~vtkPostgreSQLQueryPrivate() { if (this->QueryResults) { PQclear(this->QueryResults); } } PGresult *QueryResults; int CurrentRow; }; // ---------------------------------------------------------------------- vtkVariant vtkPostgreSQLQuery::DataValue( vtkIdType column ) { if ( this->IsActive() == false ) { vtkWarningMacro( "DataValue() called on inactive query" ); return vtkVariant(); } else if ( column < 0 || column >= this->GetNumberOfFields() ) { vtkWarningMacro( "DataValue() called with out-of-range column index " << column ); return vtkVariant(); } else if (this->QueryInternals->CurrentRow < 0) { vtkWarningMacro( "DataValue() cannot be called before advancing to the first row with NextRow()." ); return vtkVariant(); } // Since null is independent of data type, check that next if (PQgetisnull(this->QueryInternals->QueryResults, this->QueryInternals->CurrentRow, column)) { return vtkVariant(); } int colType = this->GetFieldType( column ); bool isBinary = this->IsColumnBinary(column); const char *rawData = this->GetColumnRawData(column); switch ( colType ) { case VTK_VOID: return vtkVariant(); case VTK_BIT: { return ConvertStringToBoolean(isBinary, rawData); } case VTK_CHAR: case VTK_SIGNED_CHAR: { return ConvertStringToSignedChar(isBinary, rawData); } case VTK_UNSIGNED_CHAR: { return ConvertStringToUnsignedChar(isBinary, rawData); } case VTK_SHORT: { return ConvertStringToSignedShort(isBinary, rawData); } case VTK_UNSIGNED_SHORT: { return ConvertStringToUnsignedShort(isBinary, rawData); } case VTK_INT: { return ConvertStringToSignedInt(isBinary, rawData); } case VTK_UNSIGNED_INT: { return ConvertStringToUnsignedInt(isBinary, rawData); } case VTK_LONG: { return ConvertStringToSignedLong(isBinary, rawData); } case VTK_UNSIGNED_LONG: { return ConvertStringToUnsignedLong(isBinary, rawData); } case VTK_LONG_LONG: { return ConvertStringToSignedLongLong(isBinary, rawData); } case VTK_UNSIGNED_LONG_LONG: { return ConvertStringToUnsignedLongLong(isBinary, rawData); } case VTK_FLOAT: { return ConvertStringToFloat(isBinary, rawData); } case VTK_DOUBLE: { return ConvertStringToDouble(isBinary, rawData); } case VTK_ID_TYPE: { return ConvertStringToVtkIdType(isBinary, rawData); } case VTK_STRING: { return vtkVariant(rawData); } default: { return vtkVariant(); } } // end of switch on column type } // end of DataValue(int column) // ---------------------------------------------------------------------- vtkPostgreSQLQuery::vtkPostgreSQLQuery() { this->TransactionInProgress = false; this->LastErrorText = nullptr; this->QueryInternals = nullptr; } // ---------------------------------------------------------------------- vtkPostgreSQLQuery::~vtkPostgreSQLQuery() { this->SetDatabase(nullptr); this->SetLastErrorText(nullptr); delete this->QueryInternals; } // ---------------------------------------------------------------------- void vtkPostgreSQLQuery::PrintSelf( ostream& os, vtkIndent indent ) { this->Superclass::PrintSelf(os, indent); os << indent << "Transaction in progress: " << (this->TransactionInProgress ? "YES" : "NO") << "\n"; os << indent << "Last error message: " << (this->LastErrorText ? this->LastErrorText : "(null)") << "\n"; os << indent << "Internals: "; if (this->QueryInternals) { os << this->QueryInternals; } else { os << "(null)"; } os << "\n"; } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::Execute() { if ( this->Query == 0 ) { vtkErrorMacro( "Cannot execute before a query has been set." ); return false; } vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast( this->Database ); assert( db ); // If a query is already in progress clear out its results so we can // begin anew. if (this->QueryInternals) { this->DeleteQueryResults(); } if (!db->IsOpen()) { this->SetLastErrorText("Cannot execute query. Database connection is closed."); vtkErrorMacro(<<"Cannot execute query. Database connection is closed."); this->Active = false; return false; } this->QueryInternals = new vtkPostgreSQLQueryPrivate; this->QueryInternals->QueryResults = PQexec(db->Connection->Connection, this->Query); bool returnStatus; switch (PQresultStatus(this->QueryInternals->QueryResults)) { case PGRES_EMPTY_QUERY: { returnStatus = true; this->Active = false; this->DeleteQueryResults(); vtkWarningMacro(<<"Query string was set but empty."); this->SetLastErrorText(0); }; break; case PGRES_COMMAND_OK: // success on a command returning no data { returnStatus = true; this->Active = true; this->DeleteQueryResults(); this->SetLastErrorText(0); }; break; case PGRES_TUPLES_OK: { returnStatus = true; this->Active = true; this->SetLastErrorText(0); }; break; case PGRES_BAD_RESPONSE: { returnStatus = false; this->Active = false; this->DeleteQueryResults(); this->SetLastErrorText("Incomprehensible server response"); }; break; case PGRES_FATAL_ERROR: { returnStatus = false; this->Active = false; this->SetLastErrorText(PQerrorMessage(db->Connection->Connection)); vtkErrorMacro(<<"Fatal error during query: " << this->GetLastErrorText()); this->DeleteQueryResults(); }; break; default: { returnStatus = false; this->Active = false; std::ostringstream sbuf; sbuf << "Unhandled server response: "; sbuf << PQresStatus(PQresultStatus(this->QueryInternals->QueryResults)); this->SetLastErrorText(sbuf.str().c_str()); vtkErrorMacro(<< "Unhandled server response: " << this->GetLastErrorText()); this->DeleteQueryResults(); }; break; } return returnStatus; } // ---------------------------------------------------------------------- int vtkPostgreSQLQuery::GetNumberOfFields() { if ( ! this->Active || ! this->QueryInternals ) { vtkErrorMacro( "Query is not active!" ); return 0; } return PQnfields(this->QueryInternals->QueryResults); } // ---------------------------------------------------------------------- const char* vtkPostgreSQLQuery::GetFieldName( int column ) { if ( ! this->Active || ! this->QueryInternals->QueryResults ) { vtkErrorMacro( "Query is not active!" ); return 0; } else if ( column < 0 || column >= this->GetNumberOfFields() ) { vtkErrorMacro( "Illegal field index " << column ); return 0; } return PQfname(this->QueryInternals->QueryResults, column); } // ---------------------------------------------------------------------- int vtkPostgreSQLQuery::GetFieldType( int column ) { if ( ! this->Active || ! this->QueryInternals ) { vtkErrorMacro( "Query is not active!" ); return -1; } else if ( column < 0 || column >= this->GetNumberOfFields() ) { vtkErrorMacro( "Illegal field index " << column ); return -1; } vtkPostgreSQLDatabase *db = vtkPostgreSQLDatabase::SafeDownCast(this->Database); if (!db) { vtkErrorMacro(<<"No database! How did this happen?"); return -1; } return db->Connection->GetVTKTypeFromOID(PQftype(this->QueryInternals->QueryResults, column)); } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::NextRow() { if ( ! this->IsActive() || ! this->QueryInternals ) { vtkErrorMacro( "Query is not active!" ); return false; } if (this->QueryInternals->CurrentRow < (this->GetNumberOfRows() - 1)) { ++ this->QueryInternals->CurrentRow; return true; } else { return false; } } // ---------------------------------------------------------------------- const char * vtkPostgreSQLQuery::GetLastErrorText() { if ( ! this->Database ) { return "No database"; } return this->LastErrorText; } // ---------------------------------------------------------------------- vtkStdString vtkPostgreSQLQuery::EscapeString( vtkStdString s, bool addSurroundingQuotes ) { vtkStdString retval; if ( addSurroundingQuotes ) { retval = "'"; } vtkPostgreSQLDatabase* db = static_cast( this->Database ); if ( db && db->Connection ) { char *escaped = new char[2 * s.size() + 1]; int error; PQescapeStringConn(db->Connection->Connection, escaped, s.c_str(), s.size(), &error); retval.append(escaped); delete [] escaped; if (error) { vtkErrorMacro(<<"Error while escaping string. Expect the result to be unusable."); } } else { retval.append( this->Superclass::EscapeString( s, false ) ); } if ( addSurroundingQuotes ) { retval += "'"; } return retval; } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::HasError() { if ( ! this->Database ) { return false; } return this->LastErrorText != 0; } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::BeginTransaction() { if (this->TransactionInProgress) { vtkErrorMacro(<<"Cannot start a transaction. One is already in progress."); return false; } vtkPostgreSQLDatabase* db = vtkPostgreSQLDatabase::SafeDownCast( this->Database ); assert( db ); bool status; PGresult *result = PQexec(db->Connection->Connection, BEGIN_TRANSACTION); switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: { this->SetLastErrorText(0); this->TransactionInProgress = true; status = true; }; break; case PGRES_FATAL_ERROR: { this->SetLastErrorText(PQresultErrorMessage(result)); vtkErrorMacro(<< "Error in BeginTransaction: " << this->GetLastErrorText()); status = false; }; break; default: { this->SetLastErrorText(PQresultErrorMessage(result)); vtkWarningMacro(<<"Unexpected return code " << PQresultStatus(result) << " (" << PQresStatus(PQresultStatus(result)) << ") with error message " << (this->LastErrorText ? this->LastErrorText : "(null)")); status = false; }; break; } PQclear(result); return status; } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::CommitTransaction() { if (!this->TransactionInProgress) { vtkErrorMacro(<<"Cannot commit: no transaction is in progress."); return false; } vtkPostgreSQLDatabase *db = vtkPostgreSQLDatabase::SafeDownCast(this->Database); assert(db); PGresult *result = PQexec(db->Connection->Connection, COMMIT_TRANSACTION); bool status; switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: { this->SetLastErrorText(0); this->TransactionInProgress = false; status = true; }; break; case PGRES_FATAL_ERROR: { this->SetLastErrorText(PQresultErrorMessage(result)); vtkErrorMacro(<< "Error in CommitTransaction: " << this->GetLastErrorText()); this->TransactionInProgress = false; status = false; }; break; default: { this->SetLastErrorText(PQresultErrorMessage(result)); vtkWarningMacro(<<"Unexpected return code " << PQresultStatus(result) << " (" << PQresStatus(PQresultStatus(result)) << ") with error message " << (this->LastErrorText ? this->LastErrorText : "(null)")); this->TransactionInProgress = false; status = false; }; break; } PQclear(result); return status; } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::RollbackTransaction() { if (!this->TransactionInProgress) { vtkErrorMacro(<<"Cannot rollback: no transaction is in progress."); return false; } vtkPostgreSQLDatabase *db = vtkPostgreSQLDatabase::SafeDownCast(this->Database); assert(db); PGresult *result = PQexec(db->Connection->Connection, ROLLBACK_TRANSACTION); bool status; switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: { this->SetLastErrorText(0); this->TransactionInProgress = false; status = true; }; break; case PGRES_FATAL_ERROR: { this->SetLastErrorText(PQresultErrorMessage(result)); vtkErrorMacro(<< "Error in RollbackTransaction: " << this->GetLastErrorText()); this->TransactionInProgress = false; status = false; }; break; default: { this->SetLastErrorText(PQresultErrorMessage(result)); vtkWarningMacro(<<"Unexpected return code " << PQresultStatus(result) << " (" << PQresStatus(PQresultStatus(result)) << ") with error message " << (this->LastErrorText ? this->LastErrorText : "(null)")); this->TransactionInProgress = false; status = false; }; break; } PQclear(result); return status; } // ---------------------------------------------------------------------- void vtkPostgreSQLQuery::DeleteQueryResults() { this->Active = false; delete this->QueryInternals; this->QueryInternals = nullptr; } // ---------------------------------------------------------------------- vtkVariant ConvertStringToBoolean(bool, const char *rawData) { // Since there are only a few possibilities I'm going to check // them all by hand. switch (rawData[0]) { case 'T': case 't': case 'Y': case 'y': case '1': case 1: { return vtkVariant(true); } case 'F': case 'f': case 'N': case 'n': case '0': case 0: { return vtkVariant(false); } default: { vtkGenericWarningMacro(<<"Unable to convert raw data to boolean. Data length is " << strlen(rawData) << " and string is '" << rawData << "'"); return vtkVariant(); } } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToSignedChar(bool isBinary, const char *rawData) { if (isBinary) { return vtkVariant(rawData[0]); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToChar()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToUnsignedChar(bool isBinary, const char *rawData) { if (isBinary) { return vtkVariant(static_cast(rawData[0])); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToUnsignedChar()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToSignedShort(bool isBinary, const char *rawData) { if (isBinary) { short result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToShort()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToUnsignedShort(bool isBinary, const char *rawData) { if (isBinary) { unsigned short result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToUnsignedShort()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToSignedInt(bool isBinary, const char *rawData) { if (isBinary) { int result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToInt()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToUnsignedInt(bool isBinary, const char *rawData) { if (isBinary) { unsigned int result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToUnsignedInt()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToSignedLong(bool isBinary, const char *rawData) { if (isBinary) { signed long result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToLong()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToUnsignedLong(bool isBinary, const char *rawData) { if (isBinary) { unsigned long result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToLong()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToSignedLongLong(bool isBinary, const char *rawData) { if (isBinary) { long long result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToLongLong()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToUnsignedLongLong(bool isBinary, const char *rawData) { if (isBinary) { unsigned long long result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { vtkVariant converter(rawData); return vtkVariant(converter.ToUnsignedLongLong()); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToFloat(bool isBinary, const char *rawData) { if (isBinary) { // As of PostgreSQL version 8.3.0, libpq transmits a float in network // byte order -- that is, it reinterprets the bits as an unsigned int // and then transmits them that way. This... frightens me. It assumes // that both sender and recipient use IEEE floats. Still, I'm not sure // there's any other good way to do it. unsigned int intResult = 0; ConvertFromNetworkOrder(intResult, rawData); // This is the idiom that libpq uses internally to convert between the // two types. union { unsigned int i; float f; } swap; swap.i = intResult; float floatResult = swap.f; return vtkVariant(floatResult); } else { vtkStdString rawString(rawData); float finalResult; // Catch NaN if (rawData[0] == 'N' || rawData[0] == 'n') { if (std::numeric_limits::has_quiet_NaN) { finalResult = std::numeric_limits::quiet_NaN(); } else { // C99 defines a NAN macro. If it's there, that solves our problem. #if defined(NAN) finalResult = NAN; #else float zero = 0.0; finalResult = zero / zero; #endif } } else if (rawString == "Infinity") { if (std::numeric_limits::has_infinity) { finalResult = std::numeric_limits::infinity(); } else { finalResult = VTK_FLOAT_MAX; } } else if (rawString == "-Infinity") { if (std::numeric_limits::has_infinity) { finalResult = - std::numeric_limits::infinity(); } else { finalResult = -VTK_FLOAT_MAX; } } else { // hurray, it's an ordinary float vtkVariant converter(rawData); finalResult = converter.ToFloat(); } return vtkVariant(finalResult); } // end of handling string representation } // ---------------------------------------------------------------------- vtkVariant ConvertStringToVtkIdType(bool isBinary, const char *rawData) { if (isBinary) { vtkIdType result = 0; ConvertFromNetworkOrder(result, rawData); return vtkVariant(result); } else { std::stringstream convertStream; convertStream.str(rawData); vtkIdType result; convertStream >> result; return vtkVariant(result); } } // ---------------------------------------------------------------------- vtkVariant ConvertStringToDouble(bool isBinary, const char *rawData) { if (isBinary) { // As of PostgreSQL version 8.3.0, libpq transmits a float in network // byte order -- that is, it reinterprets the bits as an unsigned int // and then transmits them that way. This... frightens me. It assumes // that both sender and recipient use IEEE floats. Still, I'm not sure // there's any other good way to do it. // Let's hope that we always have a 64-bit type. vtkTypeUInt64 intResult = 0; ConvertFromNetworkOrder(intResult, rawData); union { vtkTypeUInt64 i; double d; } swap; swap.i = intResult; return vtkVariant(swap.d); } else { double finalResult; vtkStdString rawString(rawData); // Catch NaN if (rawData[0] == 'N' || rawData[0] == 'n') { if (std::numeric_limits::has_quiet_NaN) { finalResult = std::numeric_limits::quiet_NaN(); } else { // C99 defines a NAN macro. If it's there, that solves our problem. #if defined(NAN) finalResult = NAN; #else double zero = 0.0; finalResult = zero / zero; #endif } } else if (rawString == "Infinity") { if (std::numeric_limits::has_infinity) { finalResult = std::numeric_limits::infinity(); } else { finalResult = VTK_DOUBLE_MAX; } } else if (rawString == "-Infinity") { if (std::numeric_limits::has_infinity) { finalResult = - std::numeric_limits::infinity(); } else { finalResult = -VTK_DOUBLE_MAX; } } else { // hurray, it's an ordinary double vtkVariant converter(rawData); finalResult = converter.ToDouble(); } return vtkVariant(finalResult); } // end of handling string representation } // ---------------------------------------------------------------------- bool vtkPostgreSQLQuery::IsColumnBinary(int whichColumn) { if ((!this->Active) || (!this->Database) || (!this->QueryInternals->QueryResults)) { vtkWarningMacro(<<"No active query!"); return false; } else if (whichColumn < 0 || whichColumn >= this->GetNumberOfFields()) { vtkWarningMacro(<<"Illegal column index " << whichColumn); return false; } else { return (PQfformat(this->QueryInternals->QueryResults, whichColumn) == 1); } } // ---------------------------------------------------------------------- const char * vtkPostgreSQLQuery::GetColumnRawData(int whichColumn) { if ((!this->Active) || (!this->Database) || (!this->QueryInternals->QueryResults)) { vtkWarningMacro(<<"No active query!"); return nullptr; } else if (whichColumn < 0 || whichColumn >= this->GetNumberOfFields()) { vtkWarningMacro(<<"Illegal column index " << whichColumn); return nullptr; } else { return PQgetvalue(this->QueryInternals->QueryResults, this->QueryInternals->CurrentRow, whichColumn); } } // ---------------------------------------------------------------------- int vtkPostgreSQLQuery::GetNumberOfRows() { if (!this->Database || !this->Database->IsOpen() || !this->QueryInternals || !this->Active) { vtkWarningMacro(<<"No active query. Cannot retrieve number of rows."); return 0; } else { return PQntuples(this->QueryInternals->QueryResults); } }