/*========================================================================= Program: Visualization Toolkit Module: vtkODBCQuery.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. =========================================================================*/ /* * Microsoft's own version of sqltypes.h relies on some typedefs and * macros in windows.h. This next fragment tells VTK to include the * whole thing without any of its usual #defines to keep the size * manageable. No WIN32_LEAN_AND_MEAN for us! */ #if defined(_WIN32) && !defined(__CYGWIN__) #define VTK_WINDOWS_FULL #include #endif #include "vtkODBCDatabase.h" #include "vtkODBCInternals.h" #include "vtkODBCQuery.h" #include "vtkBitArray.h" #include "vtkObjectFactory.h" #include "vtkStringArray.h" #include "vtkVariant.h" #include "vtkVariantArray.h" #include #include #include #include #include //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkODBCQuery); static vtkStdString GetErrorMessage(SQLSMALLINT handleType, SQLHANDLE handle, int* code = nullptr); /* * Bound Parameters and ODBC * * ODBC handles bound parameters by requiring that the user pass in * buffers containing data for each parameter to be bound. These are * bound to the statement using SQLBindParam. The statement must have * been prepared using SQLPrepare. Those buffers need to be freed * when they're no longer needed. * * I'm going to handle this by using my own class * (vtkODBCBoundParameter) to hold all the information the user passes * in. This is the same sort of trick I use for the MySQL bound * parameter support. At execution time I'll take the parameters and * attach them all to the statement. The vtkODBCBoundParameter * instances will each own the buffesr for their data. * * This is slightly inefficient in that it will generate * a few tiny little new[] requests. If this ever becomes a problem, * we can allocate a fixed-size buffer (8 or 16 bytes) inside * vtkODBCBoundParameter and use that for the data storage by * default. That will still require special-case handling for blobs * and strings. * * The vtkODBCQueryInternals class will handle the bookkeeping for * which parameters are and aren't bound at any given time. */ //------------------------------------------------------------------------------ class vtkODBCBoundParameter { public: vtkODBCBoundParameter() : Data(nullptr) , DataLength(0) , BufferSize(0) , DataTypeC(0) , DataTypeSQL(0) { } ~vtkODBCBoundParameter() { delete[] this->Data; } void SetData(const char* data, unsigned long size) { delete[] this->Data; this->BufferSize = size; this->DataLength = size; this->Data = new char[size]; memcpy(this->Data, data, size); } char* Data; // Buffer holding actual data unsigned long DataLength; SQLLEN BufferSize; // will be at least as large as DataLength SQLSMALLINT DataTypeC; SQLSMALLINT DataTypeSQL; }; //------------------------------------------------------------------------------ class vtkODBCQueryInternals { public: vtkODBCQueryInternals() { this->Statement = nullptr; this->CurrentRow = vtkVariantArray::New(); this->ColumnNames = vtkStringArray::New(); this->ColumnIsSigned = vtkBitArray::New(); this->ColumnTypes = nullptr; this->NullPermitted = vtkBitArray::New(); } ~vtkODBCQueryInternals() { this->FreeUserParameterList(); if (this->Statement != nullptr) { SQLFreeHandle(SQL_HANDLE_STMT, this->Statement); } this->CurrentRow->Delete(); this->ColumnNames->Delete(); this->ColumnIsSigned->Delete(); this->NullPermitted->Delete(); delete[] this->ColumnTypes; } void FreeStatement(); void FreeUserParameterList(); void ClearBoundParameters(); bool PrepareQuery(const char* queryString, SQLHANDLE connection, vtkStdString& error_message); bool SetBoundParameter(int index, vtkODBCBoundParameter* param); bool BindParametersToStatement(); SQLHANDLE Statement; vtkStdString Name; vtkVariantArray* CurrentRow; vtkStringArray* ColumnNames; vtkBitArray* ColumnIsSigned; vtkBitArray* NullPermitted; SQLSMALLINT* ColumnTypes; typedef std::vector ParameterList; ParameterList UserParameterList; }; //------------------------------------------------------------------------------ void vtkODBCQueryInternals::FreeStatement() { if (this->Statement) { SQLRETURN status; status = SQLCloseCursor(this->Statement); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errorBuf; errorBuf << "vtkODBCQuery: Unable to close SQL cursor. Error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Statement); cerr << errorBuf.str() << "\n"; } status = SQLFreeHandle(SQL_HANDLE_STMT, this->Statement); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errorBuf; errorBuf << "Unable to free statement handle. Memory leak will occur. Error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Statement); cerr << errorBuf.str() << "\n"; } this->Statement = nullptr; } } //------------------------------------------------------------------------------ #define VTK_ODBC_C_TYPENAME_MACRO(type, return_type) \ SQLSMALLINT vtkODBCTypeNameC(type) { return return_type; } VTK_ODBC_C_TYPENAME_MACRO(signed char, SQL_C_STINYINT); VTK_ODBC_C_TYPENAME_MACRO(unsigned char, SQL_C_UTINYINT); VTK_ODBC_C_TYPENAME_MACRO(signed short, SQL_C_SSHORT); VTK_ODBC_C_TYPENAME_MACRO(unsigned short, SQL_C_USHORT); VTK_ODBC_C_TYPENAME_MACRO(signed int, SQL_C_SLONG); VTK_ODBC_C_TYPENAME_MACRO(unsigned int, SQL_C_ULONG); VTK_ODBC_C_TYPENAME_MACRO(signed long, SQL_C_SLONG); VTK_ODBC_C_TYPENAME_MACRO(unsigned long, SQL_C_ULONG); VTK_ODBC_C_TYPENAME_MACRO(float, SQL_C_FLOAT); VTK_ODBC_C_TYPENAME_MACRO(double, SQL_C_DOUBLE); VTK_ODBC_C_TYPENAME_MACRO(long long, SQL_C_SBIGINT); VTK_ODBC_C_TYPENAME_MACRO(unsigned long long, SQL_C_UBIGINT); VTK_ODBC_C_TYPENAME_MACRO(const char*, SQL_C_CHAR); VTK_ODBC_C_TYPENAME_MACRO(char*, SQL_C_CHAR); VTK_ODBC_C_TYPENAME_MACRO(unsigned char*, SQL_C_CHAR); VTK_ODBC_C_TYPENAME_MACRO(void*, SQL_C_BINARY); #define VTK_ODBC_SQL_TYPENAME_MACRO(type, return_type) \ SQLSMALLINT vtkODBCTypeNameSQL(type) { return return_type; } VTK_ODBC_SQL_TYPENAME_MACRO(signed char, SQL_TINYINT); VTK_ODBC_SQL_TYPENAME_MACRO(unsigned char, SQL_TINYINT); VTK_ODBC_SQL_TYPENAME_MACRO(signed short, SQL_SMALLINT); VTK_ODBC_SQL_TYPENAME_MACRO(unsigned short, SQL_SMALLINT); VTK_ODBC_SQL_TYPENAME_MACRO(signed int, SQL_INTEGER); VTK_ODBC_SQL_TYPENAME_MACRO(unsigned int, SQL_INTEGER); VTK_ODBC_SQL_TYPENAME_MACRO(signed long, SQL_INTEGER); VTK_ODBC_SQL_TYPENAME_MACRO(unsigned long, SQL_INTEGER); VTK_ODBC_SQL_TYPENAME_MACRO(float, SQL_REAL); VTK_ODBC_SQL_TYPENAME_MACRO(double, SQL_DOUBLE); VTK_ODBC_SQL_TYPENAME_MACRO(long long, SQL_BIGINT); VTK_ODBC_SQL_TYPENAME_MACRO(unsigned long long, SQL_BIGINT); VTK_ODBC_SQL_TYPENAME_MACRO(const char*, SQL_VARCHAR); VTK_ODBC_SQL_TYPENAME_MACRO(char*, SQL_VARCHAR); VTK_ODBC_SQL_TYPENAME_MACRO(unsigned char*, SQL_VARCHAR); VTK_ODBC_SQL_TYPENAME_MACRO(void*, SQL_VARBINARY); //------------------------------------------------------------------------------ // Description: // This function will build and populate a vtkODBCBoundParameter // struct. The default implementation works for POD data types (char, // int, long, etc). I'll need to special-case strings and blobs. template vtkODBCBoundParameter* vtkBuildODBCBoundParameter(T data_value) { vtkODBCBoundParameter* param = new vtkODBCBoundParameter; param->DataTypeC = vtkODBCTypeNameC(data_value); param->DataTypeSQL = vtkODBCTypeNameSQL(data_value); param->BufferSize = sizeof(T); param->DataLength = sizeof(T); param->SetData(reinterpret_cast(&data_value), sizeof(T)); return param; } // Description: // Specialization of vtkBuildBoundParameter for nullptr-terminated // strings (i.e. CHAR and VARCHAR fields) template <> vtkODBCBoundParameter* vtkBuildODBCBoundParameter(const char* data_value) { vtkODBCBoundParameter* param = new vtkODBCBoundParameter; param->DataTypeC = SQL_C_CHAR; param->DataTypeSQL = SQL_VARCHAR; param->BufferSize = strlen(data_value); param->DataLength = static_cast(strlen(data_value)); param->SetData(data_value, static_cast(strlen(data_value))); return param; } // Description: // Alternate signature for vtkBuildBoundParameter to handle blobs vtkODBCBoundParameter* vtkBuildODBCBoundParameter( const char* data, unsigned long length, bool is_blob) { vtkODBCBoundParameter* param = new vtkODBCBoundParameter; param->DataTypeC = SQL_C_CHAR; if (is_blob) { param->DataTypeSQL = SQL_VARBINARY; } else { param->DataTypeSQL = SQL_VARCHAR; } param->BufferSize = length; param->DataLength = length; param->SetData(data, length); return param; } //------------------------------------------------------------------------------ bool vtkODBCQueryInternals::PrepareQuery( const char* queryString, SQLHANDLE dbConnection, vtkStdString& error_message) { this->FreeStatement(); this->FreeUserParameterList(); // ODBC requires that drivers either support query preparation or // emulate it to the greatest extent possible. It says nothing // about what queries may or may not be prepared. I'm going to // close my eyes and pretend that all SQL is valid for preparation // even if bound parameters don't make sense. If I'm wrong the // error messages will certainly tell me so. SQLRETURN status; status = SQLAllocHandle(SQL_HANDLE_STMT, dbConnection, &(this->Statement)); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errorBuf; errorBuf << "Unable to allocate new statement handle. Error: " << GetErrorMessage(SQL_HANDLE_DBC, dbConnection); error_message = errorBuf.str(); return false; } // Queries in VTK currently only support scrolling forward through // the results, not forward/backward/randomly. status = SQLSetStmtAttr(this->Statement, SQL_ATTR_CURSOR_TYPE, static_cast(SQL_CURSOR_FORWARD_ONLY), SQL_IS_UINTEGER); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { error_message = vtkStdString(GetErrorMessage(SQL_HANDLE_STMT, this->Statement)); return false; } // ugh, I hate having to use const_cast status = SQLPrepare(this->Statement, reinterpret_cast(const_cast(queryString)), static_cast(strlen(queryString))); if (status != SQL_SUCCESS) { std::ostringstream errorBuf; errorBuf << "Unable to prepare query for execution: " << GetErrorMessage(SQL_HANDLE_STMT, this->Statement); error_message = errorBuf.str(); return false; } else { error_message = vtkStdString(); SQLSMALLINT paramCount; status = SQLNumParams(this->Statement, ¶mCount); if (status != SQL_SUCCESS) { error_message = vtkStdString(GetErrorMessage(SQL_HANDLE_STMT, this->Statement)); return false; } else { this->UserParameterList.resize(paramCount, nullptr); return true; } } } //------------------------------------------------------------------------------ void vtkODBCQueryInternals::FreeUserParameterList() { for (unsigned int i = 0; i < this->UserParameterList.size(); ++i) { delete this->UserParameterList[i]; this->UserParameterList[i] = nullptr; } this->UserParameterList.clear(); } //------------------------------------------------------------------------------ bool vtkODBCQueryInternals::SetBoundParameter(int index, vtkODBCBoundParameter* param) { if (index >= static_cast(this->UserParameterList.size())) { vtkGenericWarningMacro(<< "ERROR: Illegal parameter index " << index << ". Did you forget to set the query?"); return false; } else { delete this->UserParameterList[index]; this->UserParameterList[index] = param; return true; } } //------------------------------------------------------------------------------ void vtkODBCQueryInternals::ClearBoundParameters() { if (this->Statement) { SQLFreeStmt(this->Statement, SQL_RESET_PARAMS); } } //------------------------------------------------------------------------------ bool vtkODBCQueryInternals::BindParametersToStatement() { if (this->Statement == nullptr) { vtkGenericWarningMacro(<< "BindParametersToStatement: No prepared statement available"); return false; } this->ClearBoundParameters(); SQLUSMALLINT numParams = static_cast(this->UserParameterList.size()); for (SQLUSMALLINT i = 0; i < numParams; ++i) { if (this->UserParameterList[i]) { SQLRETURN status = SQLBindParameter(this->Statement, i + 1, // parameter indexing starts at 1 SQL_PARAM_INPUT, this->UserParameterList[i]->DataTypeC, this->UserParameterList[i]->DataTypeSQL, 0, // column size is irrelevant 0, // decimal digits are irrelevant reinterpret_cast(this->UserParameterList[i]->Data), this->UserParameterList[i]->DataLength, &(this->UserParameterList[i]->BufferSize)); if (status != SQL_SUCCESS) { vtkGenericWarningMacro(<< "Unable to bind parameter " << i << " to SQL statement! Return code: " << status); return false; } } } return true; } //------------------------------------------------------------------------------ static vtkStdString GetErrorMessage(SQLSMALLINT handleType, SQLHANDLE handle, int* code) { SQLINTEGER sqlNativeCode = 0; SQLSMALLINT messageLength = 0; SQLRETURN status; SQLCHAR state[SQL_SQLSTATE_SIZE + 1]; SQLCHAR description[SQL_MAX_MESSAGE_LENGTH + 1]; vtkStdString finalResult; int i = 1; // There may be several error messages queued up so we need to loop // until we've got everything. std::ostringstream messagebuf; do { status = SQLGetDiagRec(handleType, handle, i, state, &sqlNativeCode, description, SQL_MAX_MESSAGE_LENGTH, &messageLength); description[SQL_MAX_MESSAGE_LENGTH] = 0; if (status == SQL_SUCCESS || status == SQL_SUCCESS_WITH_INFO) { if (code) { *code = sqlNativeCode; } if (i > 1) { messagebuf << ", "; } messagebuf << state << ' ' << description; } else if (status == SQL_ERROR || status == SQL_INVALID_HANDLE) { return vtkStdString(messagebuf.str()); } ++i; } while (status != SQL_NO_DATA); return vtkStdString(messagebuf.str()); } //------------------------------------------------------------------------------ vtkODBCQuery::vtkODBCQuery() { this->Internals = new vtkODBCQueryInternals; this->InitialFetch = true; this->LastErrorText = nullptr; this->QueryText = nullptr; } //------------------------------------------------------------------------------ vtkODBCQuery::~vtkODBCQuery() { this->SetLastErrorText(nullptr); this->SetQueryText(nullptr); delete this->Internals; } //------------------------------------------------------------------------------ void vtkODBCQuery::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); } //------------------------------------------------------------------------------ bool vtkODBCQuery::SetQuery(const char* newQuery) { this->Active = false; this->SetQueryText(newQuery); vtkODBCDatabase* db = vtkODBCDatabase::SafeDownCast(this->Database); if (db == nullptr) { vtkErrorMacro(<< "SHOULDN'T HAPPEN: SetQuery called with null database. This can only happen " "when you instantiate vtkODBCQuery directly. You should always call " "vtkODBCDatabase::GetQueryInstance to make a query object."); return false; } vtkStdString error; bool prepareStatus = this->Internals->PrepareQuery(newQuery, db->Internals->Connection, error); if (prepareStatus) { this->SetLastErrorText(nullptr); return true; } else { vtkErrorMacro(<< error.c_str()); this->SetLastErrorText(error.c_str()); return false; } } //------------------------------------------------------------------------------ const char* vtkODBCQuery::GetQuery() { return this->GetQueryText(); } //------------------------------------------------------------------------------ bool vtkODBCQuery::Execute() { // It's possible to call this function while a cursor is still open. // This is not an error, but we do need to close out the previous // cursor before opening up a new one. this->Active = false; SQLFreeStmt(this->Internals->Statement, SQL_CLOSE); this->Internals->BindParametersToStatement(); SQLRETURN status = SQLExecute(this->Internals->Statement); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errorBuf; errorBuf << "Unable to execute statement: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errorBuf.str().c_str()); this->Active = false; return false; } else { vtkDebugMacro(<< "SQLExecute succeeded."); this->Active = true; this->Internals->ColumnNames->Reset(); this->Internals->CurrentRow->Reset(); this->Internals->ColumnIsSigned->Reset(); this->Internals->NullPermitted->Reset(); delete[] this->Internals->ColumnTypes; this->Internals->ColumnTypes = nullptr; // Populate the result information now, all at once, rather than // making a whole bunch of calls later and duplicating // (potentially expensive) operations. int numColumns = this->GetNumberOfFields(); if (numColumns) { this->Internals->ColumnTypes = new SQLSMALLINT[numColumns]; this->Internals->NullPermitted->SetNumberOfTuples(numColumns); this->Internals->CurrentRow->SetNumberOfTuples(numColumns); this->Internals->ColumnNames->SetNumberOfTuples(numColumns); this->Internals->ColumnIsSigned->SetNumberOfTuples(numColumns); for (int i = 0; i < numColumns; ++i) { SQLCHAR name[1024]; SQLSMALLINT nameLength; SQLSMALLINT dataType; SQLULEN columnSize; SQLSMALLINT decimalDigits; SQLSMALLINT nullable; SQLLEN unsignedFlag = SQL_FALSE; status = SQLDescribeCol(this->Internals->Statement, i + 1, // 1-indexed, not 0 name, 1024, &nameLength, &dataType, &columnSize, &decimalDigits, &nullable); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errbuf; errbuf << "During vtkODBCQuery::Execute while looking up column " << i << ": " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); vtkErrorMacro(<< errbuf.str().c_str()); } status = SQLColAttribute( this->Internals->Statement, i + 1, SQL_DESC_UNSIGNED, nullptr, 0, nullptr, &unsignedFlag); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errbuf; errbuf << "vtkODBCQuery::Execute: Unable to get unsigned flag for column " << i << ": " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); vtkErrorMacro(<< errbuf.str().c_str()); } this->Internals->ColumnNames->SetValue(i, reinterpret_cast(name)); this->Internals->ColumnIsSigned->SetValue(i, (unsignedFlag == SQL_FALSE)); this->Internals->ColumnTypes[i] = dataType; this->Internals->NullPermitted->SetValue(i, nullable); } // done populating column information } this->SetLastErrorText(nullptr); return true; } } //------------------------------------------------------------------------------ int vtkODBCQuery::GetNumberOfFields() { if (!this->Active) { return 0; } SQLSMALLINT count; SQLRETURN status; status = SQLNumResultCols(this->Internals->Statement, &count); if (status != SQL_SUCCESS && status != SQL_SUCCESS_WITH_INFO) { std::ostringstream errbuf; errbuf << "During vtkODBCQuery::GetNumberOfFields: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); return 0; } this->SetLastErrorText(nullptr); return count; } //------------------------------------------------------------------------------ const char* vtkODBCQuery::GetFieldName(int column) { if (!this->Active) { vtkErrorMacro(<< "GetFieldName(): Query is not active!"); return nullptr; } else if (column < 0 || column >= this->GetNumberOfFields()) { vtkErrorMacro(<< "GetFieldName(): Illegal field index " << column); return nullptr; } else { return this->Internals->ColumnNames->GetValue(column).c_str(); } } //------------------------------------------------------------------------------ int vtkODBCQuery::GetFieldType(int column) { if (!this->Active) { vtkErrorMacro(<< "GetFieldType(): Query is not active!"); return VTK_VOID; } else if (column < 0 || column >= this->GetNumberOfFields()) { vtkErrorMacro(<< "GetFieldType(): Illegal field index " << column); return VTK_VOID; } else { switch (this->Internals->ColumnTypes[column]) { case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: return VTK_STRING; case SQL_INTEGER: case SQL_NUMERIC: if (this->Internals->ColumnIsSigned->GetValue(column)) { return VTK_INT; } else { return VTK_UNSIGNED_INT; } case SQL_TINYINT: if (this->Internals->ColumnIsSigned->GetValue(column)) { return VTK_SIGNED_CHAR; } else { return VTK_UNSIGNED_CHAR; } case SQL_SMALLINT: if (this->Internals->ColumnIsSigned->GetValue(column)) { return VTK_SHORT; } else { return VTK_UNSIGNED_SHORT; } case SQL_BIT: return VTK_BIT; case SQL_REAL: case SQL_FLOAT: return VTK_FLOAT; case SQL_DOUBLE: return VTK_DOUBLE; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: return VTK_STRING; case SQL_BIGINT: case SQL_DECIMAL: return VTK_TYPE_INT64; case SQL_TYPE_TIMESTAMP: // case SQL_TYPE_UTCDATETIME: // case SQL_TYPE_UTCTIME: case SQL_TYPE_DATE: case SQL_TYPE_TIME: return VTK_TYPE_UINT64; case SQL_INTERVAL_MONTH: case SQL_INTERVAL_YEAR: case SQL_INTERVAL_DAY: case SQL_INTERVAL_HOUR: case SQL_INTERVAL_MINUTE: case SQL_INTERVAL_SECOND: return VTK_TYPE_UINT64; // unhandled: SQL_INTERVAL_YEAR_TO_MONTH, // SQL_INTERVAL_DAY_TO_HOUR, SQL_INTERVAL_DAY_TO_MINUTE, // SQL_INTERVAL_DAY_TO_SECOND, SQL_INTERVAL_HOUR_TO_MINUTE, // SQL_INTERVAL_HOUR_TO_SECOND, SQL_INTERVAL_MINUTE_TO_SECOND, // SQL_GUID default: { vtkWarningMacro(<< "Unknown type " << this->Internals->ColumnTypes[column] << " returned from SQLDescribeCol"); return VTK_VOID; } } } } //------------------------------------------------------------------------------ bool vtkODBCQuery::NextRow() { if (!this->IsActive()) { vtkErrorMacro(<< "NextRow(): Query is not active!"); return false; } this->ClearCurrentRow(); SQLRETURN status = SQLFetch(this->Internals->Statement); if (status == SQL_SUCCESS) { this->SetLastErrorText(nullptr); return this->CacheCurrentRow(); } else if (status == SQL_NO_DATA) { this->SetLastErrorText(nullptr); return false; } else { return false; } } //------------------------------------------------------------------------------ vtkVariant vtkODBCQuery::DataValue(vtkIdType column) { if (!this->IsActive()) { 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 { return this->Internals->CurrentRow->GetValue(column); } } //------------------------------------------------------------------------------ void vtkODBCQuery::ClearCurrentRow() { for (vtkIdType i = 0; i < this->Internals->CurrentRow->GetNumberOfTuples(); ++i) { this->Internals->CurrentRow->SetValue(i, vtkVariant()); } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheCurrentRow() { int column; bool status = true; for (column = 0; column < this->GetNumberOfFields(); ++column) { switch (this->Internals->ColumnTypes[column]) { case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: status = status & this->CacheStringColumn(column); break; case SQL_WCHAR: case SQL_WVARCHAR: case SQL_WLONGVARCHAR: status = status & this->CacheWideStringColumn(column); break; case SQL_DECIMAL: status = status & this->CacheDecimalColumn(column); break; case SQL_NUMERIC: status = status & this->CacheNumericColumn(column); break; case SQL_SMALLINT: case SQL_INTEGER: status = status & this->CacheIntColumn(column); break; case SQL_REAL: case SQL_FLOAT: status = status & this->CacheFloatColumn(column); break; case SQL_DOUBLE: status = status & this->CacheDoubleColumn(column); break; case SQL_BIT: status = status & this->CacheBooleanColumn(column); break; case SQL_TINYINT: status = status & this->CacheCharColumn(column); break; case SQL_BIGINT: status = status & this->CacheLongLongColumn(column); break; case SQL_BINARY: case SQL_VARBINARY: case SQL_LONGVARBINARY: status = status & this->CacheBinaryColumn(column); break; case SQL_TYPE_DATE: case SQL_TYPE_TIME: case SQL_TYPE_TIMESTAMP: // case SQL_TYPE_UTCDATETIME: // case SQL_TYPE_UTCTIME: status = status & this->CacheTimeColumn(column); break; case SQL_INTERVAL_MONTH: case SQL_INTERVAL_YEAR: case SQL_INTERVAL_DAY: case SQL_INTERVAL_HOUR: case SQL_INTERVAL_MINUTE: case SQL_INTERVAL_SECOND: status = status & this->CacheIntervalColumn(column); break; default: { vtkWarningMacro(<< "DataValue: Unsupported SQL data type " << this->Internals->ColumnTypes[column] << " on column " << column); status = false; this->Internals->CurrentRow->SetValue(column, vtkVariant()); }; break; } } return status; } //------------------------------------------------------------------------------ const char* vtkODBCQuery::GetLastErrorText() { return this->LastErrorText; } //------------------------------------------------------------------------------ bool vtkODBCQuery::HasError() { return (this->LastErrorText != nullptr); } //------------------------------------------------------------------------------ bool vtkODBCQuery::BeginTransaction() { if (!this->Database->IsOpen()) { this->SetLastErrorText("Cannot begin transaction. Database is closed."); return false; } vtkODBCDatabase* db = vtkODBCDatabase::SafeDownCast(this->Database); assert(db != nullptr); SQLUINTEGER ac = SQL_AUTOCOMMIT_OFF; SQLRETURN status = SQLSetConnectAttr( db->Internals->Connection, SQL_ATTR_AUTOCOMMIT, reinterpret_cast(ac), sizeof(ac)); if (status != SQL_SUCCESS) { this->SetLastErrorText("Unable to disable autocommit."); return false; } return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CommitTransaction() { if (!this->Database->IsOpen()) { this->SetLastErrorText("Cannot commit transaction. Database is closed."); return false; } vtkODBCDatabase* db = vtkODBCDatabase::SafeDownCast(this->Database); assert(db != nullptr); SQLRETURN status; status = SQLEndTran(SQL_HANDLE_DBC, db->Internals->Connection, SQL_COMMIT); if (status != SQL_SUCCESS) { this->SetLastErrorText("Unable to commit transaction."); return false; } // After the transaction has ended we need to turn autocommit back // on so the database goes back to treating every query like a // transaction unto itself. SQLUINTEGER ac = SQL_AUTOCOMMIT_ON; status = SQLSetConnectAttr( db->Internals->Connection, SQL_ATTR_AUTOCOMMIT, reinterpret_cast(ac), sizeof(ac)); if (status != SQL_SUCCESS) { this->SetLastErrorText("Unable to re-enable autocommit."); return false; } return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::RollbackTransaction() { if (!this->Database->IsOpen()) { this->SetLastErrorText("Cannot roll back transaction. Database is closed."); return false; } vtkODBCDatabase* db = vtkODBCDatabase::SafeDownCast(this->Database); assert(db != nullptr); SQLRETURN status; status = SQLEndTran(SQL_HANDLE_DBC, db->Internals->Connection, SQL_ROLLBACK); if (status != SQL_SUCCESS) { this->SetLastErrorText("Unable to roll back transaction."); return false; } // After the transaction has ended we need to turn autocommit back // on so the database goes back to treating every query like a // transaction unto itself. SQLUINTEGER ac = SQL_AUTOCOMMIT_ON; status = SQLSetConnectAttr( db->Internals->Connection, SQL_ATTR_AUTOCOMMIT, reinterpret_cast(ac), sizeof(ac)); if (status != SQL_SUCCESS) { this->SetLastErrorText("Unable to re-enable autocommit."); return false; } return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheWideStringColumn(int column) { return CacheStringColumn(column); } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheIntColumn(int column) { SQLRETURN status; SQLINTEGER buffer; SQLLEN actualLength; SQLSMALLINT dataType; if (this->Internals->ColumnIsSigned->GetValue(column)) { dataType = SQL_C_SLONG; } else { dataType = SQL_C_ULONG; } status = SQLGetData(this->Internals->Statement, column + 1, dataType, static_cast(&buffer), sizeof(buffer), &actualLength); if (status == SQL_SUCCESS) { vtkVariant result; if (this->Internals->ColumnIsSigned->GetValue(column)) { result = vtkVariant(buffer); } else { unsigned int foo(buffer); result = vtkVariant(foo); } this->Internals->CurrentRow->SetValue(column, result); this->SetLastErrorText(nullptr); return true; } else if (status == SQL_NULL_DATA) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } else { std::ostringstream errbuf; errbuf << "CacheIntColumn (column " << column << "): ODBC error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return true; } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheLongLongColumn(int column) { SQLRETURN status; SQLLEN actualLength; int dataType; vtkVariant result; if (this->Internals->ColumnIsSigned->GetValue(column)) { dataType = SQL_C_SBIGINT; long long buffer; status = SQLGetData(this->Internals->Statement, column + 1, dataType, static_cast(&buffer), sizeof(buffer), &actualLength); result = vtkVariant(buffer); } else { dataType = SQL_C_UBIGINT; unsigned long long buffer; status = SQLGetData(this->Internals->Statement, column + 1, dataType, static_cast(&buffer), sizeof(buffer), &actualLength); result = vtkVariant(buffer); } if (status == SQL_SUCCESS) { this->Internals->CurrentRow->SetValue(column, result); this->SetLastErrorText(nullptr); return true; } else if (status == SQL_NULL_DATA) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } else { std::ostringstream errbuf; errbuf << "CacheLongLongColumn (column " << column << "): ODBC error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheCharColumn(int column) { SQLRETURN status; unsigned char buffer; SQLLEN actualLength; status = SQLGetData(this->Internals->Statement, column + 1, SQL_C_TINYINT, static_cast(&buffer), sizeof(buffer), &actualLength); if (status == SQL_SUCCESS) { vtkVariant result; if (this->Internals->ColumnIsSigned->GetValue(column)) { result = vtkVariant(buffer); } else { unsigned char foo = buffer; result = vtkVariant(foo); } this->Internals->CurrentRow->SetValue(column, result); this->SetLastErrorText(nullptr); return true; } else if (status == SQL_NULL_DATA) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } else { std::ostringstream errbuf; errbuf << "CacheCharColumn (column " << column << "): ODBC error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheBooleanColumn(int column) { SQLRETURN status; unsigned char buffer; SQLLEN actualLength; status = SQLGetData(this->Internals->Statement, column + 1, SQL_C_TINYINT, static_cast(&buffer), sizeof(buffer), &actualLength); if (status == SQL_SUCCESS) { vtkVariant result(buffer != 0); this->Internals->CurrentRow->SetValue(column, result); this->SetLastErrorText(nullptr); return true; } else if (status == SQL_NULL_DATA) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } else { std::ostringstream errbuf; errbuf << "CacheCharColumn (column " << column << "): ODBC error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheFloatColumn(int column) { SQLRETURN status; SQLFLOAT buffer; SQLLEN actualLength; status = SQLGetData(this->Internals->Statement, column + 1, (sizeof(buffer) == sizeof(double) ? SQL_C_DOUBLE : SQL_C_FLOAT), static_cast(&buffer), sizeof(buffer), &actualLength); if (status == SQL_SUCCESS) { vtkVariant result(buffer); this->Internals->CurrentRow->SetValue(column, result); this->SetLastErrorText(nullptr); return true; } else if (status == SQL_NULL_DATA) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } else { std::ostringstream errbuf; errbuf << "CacheFloatColumn (column " << column << "): ODBC error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheDoubleColumn(int column) { SQLRETURN status; SQLDOUBLE buffer; SQLLEN actualLength; status = SQLGetData(this->Internals->Statement, column + 1, SQL_C_DOUBLE, static_cast(&buffer), sizeof(buffer), &actualLength); if (status == SQL_SUCCESS) { vtkVariant result(buffer); this->Internals->CurrentRow->SetValue(column, result); this->SetLastErrorText(nullptr); return true; } else if (status == SQL_NULL_DATA) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } else { std::ostringstream errbuf; errbuf << "CacheDoubleColumn (column " << column << "): ODBC error: " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheStringColumn(int column) { SQLRETURN status; SQLLEN bufferLength; SQLLEN indicator; vtkStdString result; std::ostringstream outbuf; bufferLength = 65536; // this is a pretty reasonable compromise // between the expense of ODBC requests and // application memory usage std::vector buffer(bufferLength); while (true) { status = SQLGetData(this->Internals->Statement, column + 1, SQL_C_CHAR, static_cast(buffer.data()), bufferLength, &indicator); /* cerr << "once around the read loop for column " << column << ": status " << status << ", indicator " << indicator << "\n"; */ int bytesToWrite = 0; if (status == SQL_SUCCESS || status == SQL_SUCCESS_WITH_INFO) { if (status == SQL_NO_DATA) { // done reading! break; } else if (indicator == SQL_NULL_DATA) { // cerr << "Wide string value for column " << column << " is null\n"; break; } // If we get to this point then there's data to read. if (indicator == SQL_NO_TOTAL) { bytesToWrite = bufferLength; } else if (indicator > bufferLength) { bytesToWrite = bufferLength; } else { bytesToWrite = indicator; } // cerr << "Writing " << bytesToWrite << " characters in string column\n"; if (status == SQL_SUCCESS_WITH_INFO) { // eat the null terminator bytesToWrite -= 1; } outbuf.write(buffer.data(), bytesToWrite); if (status == SQL_SUCCESS) { // we retrieved everything in one pass break; } } else if (status == SQL_ERROR) { // there was some sort of error std::ostringstream errbuf; errbuf << "Error while reading wide string column " << column << ": " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); cerr << errbuf.str() << "\n"; this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } else if (status == SQL_INVALID_HANDLE) { this->SetLastErrorText("CacheWideStringColumn: Attempted to read from invalid handle!"); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } this->Internals->CurrentRow->SetValue(column, vtkVariant(outbuf.str())); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheBinaryColumn(int column) { SQLRETURN status; vtkStdString result; SQLSMALLINT nameLength; SQLSMALLINT columnType; SQLULEN columnSize; SQLSMALLINT columnScale; SQLSMALLINT nullable; SQLLEN indicator; SQLTCHAR namebuf[1024]; status = SQLDescribeCol(this->Internals->Statement, column + 1, namebuf, 1024, &nameLength, &columnType, &columnSize, &columnScale, &nullable); if (status != SQL_SUCCESS) { std::ostringstream errbuf; errbuf << "CacheBinaryColumn: Unable to describe column " << column << ": " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(errbuf.str().c_str()); return false; } // If the data is smaller than 64k just read it in one // chunk. Otherwise read it in multiple passes. if (columnSize == 0) { columnSize = 256; // maybe it can't be determined } else if (columnSize > 65536) { columnSize = 65536; // read in 64k chunks } std::vector buffer(columnSize); this->SetLastErrorText(nullptr); std::ostringstream outbuf; while (true) { status = SQLGetData(this->Internals->Statement, column + 1, SQL_C_CHAR, static_cast(buffer.data()), columnSize, &indicator); /* cerr << "once around the read loop for column " << column << ": status " << status << ", indicator " << indicator << "\n"; */ int bytesToWrite = 0; if (status == SQL_SUCCESS || status == SQL_SUCCESS_WITH_INFO) { if (status == SQL_NO_DATA) { // done reading! break; } else if (indicator == SQL_NULL_DATA) { // cerr << "Wide string value for column " << column << " is null\n"; break; } // If we get to this point then there's data to read. if (indicator == SQL_NO_TOTAL) { bytesToWrite = columnSize; } else if (indicator > static_cast(columnSize)) { bytesToWrite = columnSize; } else { bytesToWrite = indicator; } // cerr << "Writing " << bytesToWrite << " characters in string column\n"; if (status == SQL_SUCCESS_WITH_INFO) { // eat the null terminator bytesToWrite -= 1; } outbuf.write(buffer.data(), bytesToWrite); if (status == SQL_SUCCESS) { // we retrieved everything in one pass break; } } else if (status == SQL_ERROR) { // there was some sort of error std::ostringstream errbuf; errbuf << "Error while reading binary column " << column << ": " << GetErrorMessage(SQL_HANDLE_STMT, this->Internals->Statement); this->SetLastErrorText(errbuf.str().c_str()); cerr << errbuf.str() << "\n"; this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } else if (status == SQL_INVALID_HANDLE) { this->SetLastErrorText("CacheWideStringColumn: Attempted to read from invalid handle!"); this->Internals->CurrentRow->SetValue(column, vtkVariant()); return false; } } this->Internals->CurrentRow->SetValue(column, vtkVariant(outbuf.str())); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheDecimalColumn(int column) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheNumericColumn(int column) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheTimeColumn(int column) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::CacheIntervalColumn(int column) { this->Internals->CurrentRow->SetValue(column, vtkVariant()); this->SetLastErrorText(nullptr); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, unsigned char value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, signed char value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, unsigned short value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, signed short value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, unsigned int value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, signed int value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, unsigned long value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, signed long value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, unsigned long long value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, long long value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, float value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, double value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, const char* value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, const vtkStdString& value) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter(value)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, const char* data, size_t length) { this->Internals->SetBoundParameter( index, vtkBuildODBCBoundParameter(data, static_cast(length), false)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::BindParameter(int index, const void* data, size_t length) { this->Internals->SetBoundParameter(index, vtkBuildODBCBoundParameter( reinterpret_cast(data), static_cast(length), true)); return true; } //------------------------------------------------------------------------------ bool vtkODBCQuery::ClearParameterBindings() { this->Internals->ClearBoundParameters(); return true; }