/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ /***************************************************************************** * * $/Plasma20/Sources/Plasma/NucleusLib/pnSqlLib/Private/pnSqlConn.cpp * ***/ #include "../Pch.h" #pragma hdrstop //#define LOG_SQL_STMTS //#define ODBC_TRACING #define ORACLE_FREE_DRIVER /***************************************************************************** * * Private * ***/ static unsigned kConnectionTaskCount = 10; static unsigned kCheckDeadTaskCount = 5; static unsigned kLoginTimeoutSecs = 30; static unsigned kConnectionTimeoutSecs = 60; struct SqlState { SQLCHAR buffer[6]; }; struct SqlStmtHash { FSqlBindPrepare prepare; SqlStmtHash (); SqlStmtHash (FSqlBindPrepare prepare); dword GetHash () const; bool operator== (const SqlStmtHash & rhs) const; }; static const unsigned kStmtFlagBindColMode = 1<<0; static const unsigned kStmtFlagBindParamMode = 1<<1; static const unsigned kStmtFlagDriverConnected = 1<<2; static const unsigned kStmtFlagDelete = 1<<3; static const unsigned kStmtFlagReset = 1<<4; struct SqlStmt : SqlStmtHash { HASHLINK(SqlStmt) hashLink; LINK(SqlStmt) listLink; SqlConn * conn; SQLHDBC hdbc; SQLHSTMT hstmt; unsigned flags; int bindCol; int bindParam; FSqlConnErrorHandler errHandler; void * errHandlerParam; unsigned sequence; unsigned timeoutSecs; byte * data; wchar debug[128]; SqlStmt (SqlConn * conn); ~SqlStmt (); SqlStmt & operator= (const SqlStmt &); // not impl. bool Initialize (const wchar connectStr[]); void Reset (); bool QueryDead (unsigned method); }; static const unsigned kConnFlagInvalid = 1<<0; struct SqlConn { CCritSect critsect; SQLHENV henv; unsigned flags; unsigned desiredConns; unsigned currentConns; unsigned connectTasks; unsigned checkDeadTasks; unsigned sequence; unsigned timeoutSecs; AsyncThreadTaskList * taskList; wchar * connectStr; LISTDECL( SqlStmt, listLink ) stmts; HASHTABLEDECL( SqlStmt, SqlStmtHash, hashLink ) freeStmts; SqlConn ( const wchar connectStr[], SQLHENV henv ); ~SqlConn (); bool GrowConnections_CS (unsigned attempts); void AsyncGrowConnections_CS (); void CheckDead_CS (); void AsyncCheckDead_CS (); }; /***************************************************************************** * * Private data * ***/ static long s_perf[kSqlConnNumPerf]; /***************************************************************************** * * Logging * ***/ //============================================================================ static bool EnumDiagRec ( SQLSMALLINT handleType, SQLHANDLE handle, unsigned index, unsigned code, SQLSMALLINT destChars, SQLCHAR dest[], SQLINTEGER * native, SqlState * state ) { SQLSMALLINT msgLen; int result = SQLGetDiagRec( handleType, handle, (SQLSMALLINT) index, state->buffer, native, dest, destChars, &msgLen ); if (result == SQL_NO_DATA) return false; if (!SQL_SUCCEEDED(result)) { LogMsg(kLogError, "Sql(%u) %d, DiagRec", code, result); return false; } return true; } //============================================================================ static void LogErr ( int result, SQLSMALLINT handleType, SQLHANDLE handle, const wchar function[], const wchar command[] ) { if (SQL_SUCCEEDED(result) && result != SQL_SUCCESS_WITH_INFO) return; if (result == SQL_NEED_DATA) return; if (result == SQL_NO_DATA) return; static long s_code = 1; long code = AtomicAdd(&s_code, 1); ELogSeverity severity = result == SQL_SUCCESS_WITH_INFO ? kLogPerf : kLogError; LogMsg(severity, "Sql(%u) %d, %S, %S", code, result, function, command); for (int index = 1;; ++index) { // Get next message SqlState state; SQLINTEGER native; SQLCHAR msgStr[512]; const bool indexValid = EnumDiagRec( handleType, handle, index, code, arrsize(msgStr), msgStr, &native, &state ); if (!indexValid) break; LogMsg(severity, " Sql(%u) %d, %s, %s", code, native, state.buffer, msgStr); } } //============================================================================ static void LogStmtError ( int result, SqlStmt * stmt, const wchar function[] ) { if (result == SQL_SUCCESS) return; if (result == SQL_NEED_DATA) return; if (result == SQL_NO_DATA) return; // Once a statement has an error, it is possible for it to get "wedged"; // so reset the statement after this transaction to make it happy. stmt->flags |= kStmtFlagReset; // Get any diagnostics from SQL ARRAY(char) diagRecs; for (int index = 1;; ++index) { // Get next message SqlState state; SQLINTEGER native; SQLCHAR msgStr[512]; const bool indexValid = EnumDiagRec( SQL_HANDLE_STMT, stmt->hstmt, index, 0, arrsize(msgStr), msgStr, &native, &state ); if (!indexValid) break; diagRecs.Add((const char *)msgStr, StrLen((const char *) msgStr) + 1); } diagRecs.Add(L'\0'); // If the statement doesn't have a handler, handle it the default way if (!stmt->errHandler) { LogErr(result, SQL_HANDLE_STMT, stmt->hstmt, function, stmt->debug); return; } // Send the error to the statement's handler stmt->errHandler( result, function, stmt->debug, diagRecs.Ptr(), stmt->errHandlerParam ); } /***************************************************************************** * * SqlStmtHash * ***/ //============================================================================ inline SqlStmtHash::SqlStmtHash () : prepare(nil) {} //============================================================================ inline SqlStmtHash::SqlStmtHash (FSqlBindPrepare prepare) : prepare(prepare) {} //============================================================================ inline dword SqlStmtHash::GetHash () const { return (dword) prepare; } //============================================================================ inline bool SqlStmtHash::operator== (const SqlStmtHash & rhs) const { return prepare == rhs.prepare; } /***************************************************************************** * * SqlStmt * ***/ //============================================================================ SqlStmt::SqlStmt (SqlConn * conn) : conn(conn) , hdbc(nil) , hstmt(nil) , flags(0) , bindCol(0) , bindParam(0) , timeoutSecs(0) , data(nil) , errHandler(nil) , errHandlerParam(nil) { debug[0] = 0; if (conn) { // This is a real statement, so add it to the statement count ++conn->currentConns; sequence = conn->sequence; } else { // This statement is a list marker object sequence = 0; } } //============================================================================ SqlStmt::~SqlStmt () { int result; Reset(); if (hstmt) { result = SQLFreeHandle(SQL_HANDLE_STMT, hstmt); LogStmtError(result, this, L"SQLFreeHandle"); AtomicAdd(&s_perf[kSqlConnPerfConnCurrent], -1); } if (flags & kStmtFlagDriverConnected) { ASSERT(hdbc); result = SQLDisconnect(hdbc); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLDisconnect", debug); } if (hdbc) { result = SQLFreeHandle(SQL_HANDLE_DBC, hdbc); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLFreeHandle", debug); } if (conn) --conn->currentConns; } //============================================================================ bool SqlStmt::Initialize (const wchar connectStr[]) { ASSERT(!hdbc); for (;;) { // Allocate connection handle int result; result = SQLAllocHandle( SQL_HANDLE_DBC, conn->henv, &hdbc ); LogErr(result, SQL_HANDLE_ENV, conn->henv, L"SQLAllocHandle(dbc)", debug); if (!SQL_SUCCEEDED(result)) break; // Set login timeout /* -- Crashes EasySoft Oracle driver -- Not supported by Oracle's driver result = SQLSetConnectAttr( hdbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) &kLoginTimeoutSecs, SQL_IS_UINTEGER ); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLSetConnectAttr(loginTimeout)", debug); if (!SQL_SUCCEEDED(result)) break; */ #ifdef ODBC_TRACING result = SQLSetConnectAttr( hdbc, SQL_ATTR_TRACEFILE, "odbc.log", SQL_NTS ); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLSetConnectAttr(tracefile)", debug); result = SQLSetConnectAttr( hdbc, SQL_ATTR_TRACE, (SQLPOINTER) SQL_OPT_TRACE_ON, SQL_IS_INTEGER ); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLSetConnectAttr(trace)", debug); #endif /* -- Crashes EasySoft Oracle driver -- Not supported by Oracle's driver result = SQLSetConnectAttr( hdbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) &kConnectionTimeoutSecs, SQL_IS_UINTEGER ); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLSetConnectAttr(connectTimeout)", debug); if (!SQL_SUCCEEDED(result)) break; */ // Perform driver connection SQLWCHAR outConnectStr[1024]; SQLSMALLINT outConnectLen; result = SQLDriverConnectW( hdbc, (HWND) nil, (SQLWCHAR *) connectStr, SQL_NTS, outConnectStr, (SQLSMALLINT) arrsize(outConnectStr), &outConnectLen, SQL_DRIVER_NOPROMPT ); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLDriverConnect()", debug); if (!SQL_SUCCEEDED(result)) break; flags |= kStmtFlagDriverConnected; // Create statement for connection result = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt ); LogStmtError(result, this, L"SQLAllocHandle(stmt)"); if (!SQL_SUCCEEDED(result)) { ASSERT(!hstmt); break; } // Success! AtomicAdd(&s_perf[kSqlConnPerfConnCurrent], 1); return true; } // Failure! return false; } //============================================================================ void SqlStmt::Reset () { flags &= ~kStmtFlagReset; prepare = nil; SqlConnBindColReset(this); SqlConnBindParameterReset(this); #ifdef ORACLE_FREE_DRIVER { // Oracle's driver can't handle cached stmts, so blow it all away. int result; // Destroy old statement if (hstmt) { result = SQLFreeHandle(SQL_HANDLE_STMT, hstmt); LogStmtError(result, this, L"SQLFreeHandle"); } // Create new statement result = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt ); LogStmtError(result, this, L"SQLAllocHandle(stmt)"); if (!SQL_SUCCEEDED(result)) ASSERT(!hstmt); } #endif // ORACLE_FREE_DRIVER FREE(data); data = nil; debug[0] = 0; } //============================================================================ bool SqlStmt::QueryDead (unsigned method) { SQLUINTEGER dead; int result = SQLGetConnectAttr( hdbc, method, &dead, SQL_IS_UINTEGER, nil ); LogErr(result, SQL_HANDLE_DBC, hdbc, L"SQLGetConnectAttr(dead)", debug); if (!SQL_SUCCEEDED(result)) return false; return dead == SQL_CD_TRUE; } /***************************************************************************** * * StmtConn * ***/ //============================================================================ SqlConn::SqlConn ( const wchar connectStr[], SQLHENV henv ) : henv(henv) , flags(0) , desiredConns(0) , currentConns(0) , connectTasks(0) , checkDeadTasks(0) , sequence(0) , timeoutSecs(0) , connectStr(StrDup(connectStr)) , taskList(AsyncThreadTaskListCreate()) {} //=========================================================================== SqlConn::~SqlConn () { ASSERT(!freeStmts.Head()); ASSERT(!stmts.Head()); DEL(connectStr); SQLFreeHandle(SQL_HANDLE_ENV, henv); AsyncThreadTaskListDestroy(taskList, kNetErrRemoteShutdown); AtomicAdd(&s_perf[kSqlConnPerfConnDesired], - (long) desiredConns); } /**************************************************************************** * * Connection management functions * ***/ //============================================================================ bool SqlConn::GrowConnections_CS (unsigned attempts) { for (unsigned i = 0; i < attempts; ++i) { // If the maximum number of connections has been reached then bail if (currentConns >= desiredConns) break; // If the server decided to shut down then bail if (flags & kConnFlagInvalid) break; // Leave the critical section to perform connection SqlStmt * stmt = NEW(SqlStmt)(this); // Ensure that the connection string is valid // for the duration of the initialization by // creating a reference to the string. critsect.Leave(); bool result = stmt->Initialize(connectStr); critsect.Enter(); // If the connection failed then bail if (!result) { DEL(stmt); return false; } // Add new connection to free list freeStmts.Add(stmt); stmts.Link(stmt); } // Return true only if all connection attempts were successful return true; } //============================================================================ static void SqlConnConnectCallback (void * param, ENetError error) { SqlConn & conn = * (SqlConn *) param; AtomicAdd(&s_perf[kSqlConnPerfConnPending], 1); conn.critsect.Enter(); { if (!error) conn.GrowConnections_CS((unsigned) -1); conn.connectTasks -= 1; } conn.critsect.Leave(); AtomicAdd(&s_perf[kSqlConnPerfConnPending], -1); } //============================================================================ void SqlConn::AsyncGrowConnections_CS () { // If the conn structure is being destroyed // then it cannot initiate any new connections if (flags & kConnFlagInvalid) return; // How many additional tasks need to be started in // order to reach the desired number of connections? unsigned tasks = desiredConns - currentConns - connectTasks; if ((int) tasks <= 0) return; // Set upper bound on connection tasks if (connectTasks + tasks > kConnectionTaskCount) tasks = kConnectionTaskCount - connectTasks; // Initiate connection tasks connectTasks += tasks; for (unsigned i = 0; i < tasks; ++i) { AsyncThreadTaskAdd( taskList, SqlConnConnectCallback, this, L"pnSqlConn.SqlConn::AsyncGrowConnections_CS" ); } } /**************************************************************************** * * CheckDead management functions * ***/ //============================================================================ void SqlConn::CheckDead_CS () { // Use a list-position marker to allow this function to // safely walk through the linked list even though it // leaves the critical section. SqlStmt marker(nil); stmts.Link(&marker, kListHead); while (SqlStmt * stmt = marker.listLink.Next()) { // Move the marker to the next location stmts.Link(&marker, kListLinkAfter, stmt); // The statement can't be used if either: // - it is in use // - it is a marker if (!stmt->hashLink.IsLinked()) continue; // If the statement has already been processed during // this generation then do not process it again if ((int) (sequence - stmt->sequence) <= 0) continue; // Prevent this statement from being used during check stmt->sequence = sequence; stmt->hashLink.Unlink(); // Leave the critical section to check connection critsect.Leave(); bool dead = stmt->QueryDead(SQL_ATTR_CONNECTION_DEAD); critsect.Enter(); // Deallocate the statement if it's bogus if (dead) { DEL(stmt); AsyncGrowConnections_CS(); } else { freeStmts.Add(stmt); } // If the server decided to shut down then bail if (flags & kConnFlagInvalid) break; } } //============================================================================ static void SqlConnCheckDeadCallback (void * param, ENetError error) { SqlConn & conn = * (SqlConn *) param; AtomicAdd(&s_perf[kSqlConnPerfConnCheckDead], 1); conn.critsect.Enter(); { if (!error) conn.CheckDead_CS(); conn.checkDeadTasks -= 1; } conn.critsect.Leave(); AtomicAdd(&s_perf[kSqlConnPerfConnCheckDead], -1); } //============================================================================ void SqlConn::AsyncCheckDead_CS () { // Don't start new checking tasks while there are others outstanding if (checkDeadTasks) return; // Start the next generation of check-dead tasks ++sequence; checkDeadTasks = kCheckDeadTaskCount; for (unsigned i = 0; i < kCheckDeadTaskCount; ++i) { AsyncThreadTaskAdd( taskList, SqlConnCheckDeadCallback, this, L"pnSqlConn.SqlConn::AsyncCheckDead_CS" ); } } /***************************************************************************** * * Exports * ***/ //============================================================================ SqlConn * SqlConnCreate ( const wchar connectStr[], unsigned maxConnections, unsigned transTimeoutSeconds ) { ASSERT(connectStr); ASSERT(maxConnections); /* Connection pooling not needed because connections are held for the entire duration of the program. SQLSetEnvAttr( nil, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) SQL_CP_ONE_PER_DRIVER, SQL_IS_INTEGER ); */ // Allocate environment SQLHENV henv; int result = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv ); LogErr(result, SQL_HANDLE_ENV, henv, L"SQLAllocHandle(env)", L""); if (!SQL_SUCCEEDED(result)) return nil; // Set ODBC version result = SQLSetEnvAttr( henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0 ); LogErr(result, SQL_HANDLE_ENV, henv, L"SQLSetEnvAttr(version)", L""); // Allocate a connection record SqlConn * conn = NEW(SqlConn)(connectStr, henv); // Update configuration options conn->critsect.Enter(); { // Update timeout conn->timeoutSecs = transTimeoutSeconds; // Update connection count int delta = (int) (maxConnections - conn->desiredConns); AtomicAdd(&s_perf[kSqlConnPerfConnDesired], delta); conn->desiredConns += delta; // Create more connections conn->AsyncGrowConnections_CS(); } conn->critsect.Leave(); return conn; } //============================================================================ void SqlConnShutdown (SqlConn * conn) { conn->critsect.Enter(); { conn->flags |= kConnFlagInvalid; SqlStmt * stmt = conn->stmts.Head(); for (; stmt; stmt = conn->stmts.Next(stmt)) SQLCancel(stmt->hstmt); } conn->critsect.Leave(); } //============================================================================ void SqlConnDestroy (SqlConn * conn) { for (;; AsyncSleep(1)) { bool wait; conn->critsect.Enter(); { // Signal shutdown conn->flags |= kConnFlagInvalid; // Delete free and disconnected statements conn->freeStmts.Clear(); // Wait until tasks are complete and statements have been destroyed wait = conn->currentConns || conn->connectTasks || conn->checkDeadTasks; } conn->critsect.Leave(); if (wait) continue; // Complete! break; } DEL(conn); } //============================================================================ void SqlConnFreeStmt (SqlStmt * stmt) { ASSERT(stmt); ASSERT(stmt->listLink.IsLinked()); ASSERT(!stmt->hashLink.IsLinked()); SqlConnCloseCursor(stmt); #ifdef ORACLE_FREE_DRIVER { // Oracle's driver can't handle cached stmts, so blow it all away. stmt->flags |= kStmtFlagReset; } #endif // ORACLE_FREE_DRIVER // If this statement was used for an ad hoc query // then it can't be cached, so reset the bindings if (!stmt->prepare || (stmt->flags & kStmtFlagReset)) stmt->Reset(); bool dead = (stmt->flags & kStmtFlagDelete) || stmt->QueryDead(SQL_ATTR_CONNECTION_DEAD); SqlConn * conn = stmt->conn; conn->critsect.Enter(); { if (stmt->flags & kStmtFlagDelete) { DEL(stmt); conn->AsyncGrowConnections_CS(); } else if (dead) { DEL(stmt); conn->AsyncCheckDead_CS(); } else { conn->freeStmts.Add(stmt); } } conn->critsect.Leave(); } //============================================================================ void SqlStmtSetAttr ( SqlStmt * stmt, int attr, void * valPtr, int strLen ) { ASSERT(stmt); ASSERT(!stmt->prepare); int result = SQLSetStmtAttr( stmt->hstmt, (SQLINTEGER)attr, (SQLPOINTER)valPtr, (SQLINTEGER)strLen ); LogErr(result, SQL_HANDLE_STMT, stmt->hstmt, L"SQLSetStmtAttr", nil); } //============================================================================ SqlStmt * SqlConnAllocStmt (SqlConn * conn) { return SqlConnAllocStmt(conn, nil, nil); } //============================================================================ SqlStmt * SqlConnAllocStmt ( SqlConn * conn, FSqlBindPrepare prepare, byte ** bindData // [OUT] ) { ASSERT(prepare || !bindData); if (!conn) return nil; SqlStmt * stmt; FSqlBindPrepare reset = nil; FSqlBindPrepare cached = prepare; unsigned timeoutSeconds = 0; conn->critsect.Enter(); { // Get the transaction timeout value timeoutSeconds = conn->timeoutSecs; for (;;) { // Is the server permanently dead? if (conn->flags & kConnFlagInvalid) { prepare = nil; stmt = nil; break; } // If there is already a cached statement that has // been prepared with the same data then recycle it if (prepare && (nil != (stmt = conn->freeStmts.Find(SqlStmtHash(prepare))))) { *bindData = stmt->data; prepare = nil; conn->freeStmts.Unlink(stmt); break; } // If there is a statement that hasn't been prepared then use it now if (nil != (stmt = conn->freeStmts.Find(SqlStmtHash(nil)))) { ASSERT(!stmt->data); conn->freeStmts.Unlink(stmt); break; } // Take the least recently used item from the table and reinitialize if (nil != (stmt = conn->freeStmts.Head())) { ASSERT(stmt->prepare); ASSERT(stmt->data); reset = stmt->prepare; conn->freeStmts.Unlink(stmt); break; } if (conn->desiredConns == conn->currentConns) { // Since there aren't enough statements on the free list, // allocate new connections to the server. Since this is // a very time-consuming operation, and it means that there // isn't a surplus of connections for caching (more connections // than threads, so that statements can be cached), this is a // very serious error. LogMsg(kLogError, "SqlConn: no free statements"); AtomicAdd(&s_perf[kSqlConnPerfConnDesired], 50); conn->desiredConns += 50; } // Don't allow too many threads to be performing connection tasks // so that this module doesn't starve the rest of the system bool result = conn->connectTasks < kConnectionTaskCount; if (result) { // There is a crash bug where when the first connection to the // server is allocated and then immediately used, but it occurs // on the second use of the statement. I have no idea why this // bug occurs. ++conn->connectTasks; conn->AsyncGrowConnections_CS(); result = conn->GrowConnections_CS(5); --conn->connectTasks; } // Did the connection attempt fail? if (!result) { prepare = nil; stmt = nil; break; } } // If at any time there aren't connections then create more of them! if (stmt && (conn->desiredConns != conn->currentConns)) conn->AsyncGrowConnections_CS(); } conn->critsect.Leave(); // If no statement could be allocated then bail if (!stmt) return nil; // Ensure that statement was removed from the free list ASSERT(!stmt->hashLink.IsLinked()); if (reset) { stmt->Reset(); } if (stmt->timeoutSecs != timeoutSeconds) { stmt->timeoutSecs = timeoutSeconds; int result = SQLSetConnectAttr( stmt->hdbc, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER) timeoutSeconds, SQL_IS_UINTEGER ); LogErr(result, SQL_HANDLE_DBC, stmt->hdbc, L"SQLSetConnectAttr(queryTimeout)", stmt->debug); } if (prepare) { ARRAY(byte) data; prepare(stmt, &data); stmt->prepare = prepare; stmt->data = data.Detach(); *bindData = stmt->data; AtomicAdd(&s_perf[kSqlConnPerfCacheMisses], 1); } else if (cached) { AtomicAdd(&s_perf[kSqlConnPerfCacheHits], 1); } return stmt; } //============================================================================ void SqlConnSetErrorHandler ( SqlStmt * stmt, FSqlConnErrorHandler handler, void * userParam ) { stmt->errHandler = handler; stmt->errHandlerParam = userParam; } //============================================================================ void SqlConnBindColReset (SqlStmt * stmt) { ASSERT(!stmt->prepare); if (stmt->bindCol) { SQLFreeStmt(stmt->hstmt, SQL_UNBIND); stmt->bindCol = 0; } if (stmt->flags & kStmtFlagBindColMode) { SqlConnBindColMode( stmt, nil, // rowCountPtr 0, // structSize 1, // arraySize nil // statusArrayPtr ); stmt->flags &= ~kStmtFlagBindColMode; } } //============================================================================ void SqlConnBindColMode ( SqlStmt * stmt, SQLUINTEGER * rowCount, // SQL_ATTR_ROWS_FETCHED_PTR SQLUINTEGER structSize, // SQL_ATTR_ROW_BIND_TYPE SQLUINTEGER arraySize, // SQL_ATTR_ROW_ARRAY_SIZE SQLUSMALLINT * statusArray // SQL_ATTR_ROW_STATUS_PTR ) { ASSERT(!stmt->prepare); // Bind mode set to non-default value, so it // must be reset before statement is re-used stmt->flags |= kStmtFlagBindColMode; // Set rowCount int result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_ROWS_FETCHED_PTR, rowCount, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); // Set row/col binding result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER) structSize, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); // Set number of elements in parameter arrays result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) arraySize, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); // Set status array result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_ROW_STATUS_PTR, statusArray, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); } //============================================================================ void SqlConnBindCol ( SqlStmt * stmt, SQLINTEGER targetType, SQLPOINTER targetValue, SQLINTEGER bufferLength, SQLINTEGER * indPtr ) { ASSERT(!stmt->prepare); int result = SQLBindCol( stmt->hstmt, (SQLUSMALLINT) ++stmt->bindCol, (SQLSMALLINT) targetType, targetValue, bufferLength, indPtr ); LogStmtError(result, stmt, L"SQLBindCol"); } //============================================================================ void SqlConnBindParameterReset (SqlStmt * stmt) { ASSERT(!stmt->prepare); if (stmt->bindParam) { SQLFreeStmt(stmt->hstmt, SQL_RESET_PARAMS); stmt->bindParam = 0; } if (stmt->flags & kStmtFlagBindParamMode) { SqlConnBindParameterMode( stmt, nil, // processCountPtr 0, // structSize 1, // arraySize nil // statusArrayPtr ); stmt->flags &= ~kStmtFlagBindParamMode; } } //============================================================================ void SqlConnBindParameterMode ( SqlStmt * stmt, SQLUINTEGER * processCount, // SQL_ATTR_PARAMS_PROCESSED_PTR SQLUINTEGER structSize, // SQL_ATTR_PARAM_BIND_TYPE SQLUINTEGER arraySize, // SQL_ATTR_PARAMSET_SIZE SQLUSMALLINT * statusArray // SQL_ATTR_PARAM_STATUS_PTR ) { ASSERT(!stmt->prepare); // Bind mode set to non-default value, so it // must be reset before statement is re-used stmt->flags |= kStmtFlagBindParamMode; // Set processCount int result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_PARAMS_PROCESSED_PTR, processCount, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); // Set row/col binding result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER) structSize, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); // Set number of elements in parameter arrays result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) arraySize, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); // Set status array result = SQLSetStmtAttr(stmt->hstmt, SQL_ATTR_PARAM_STATUS_PTR, statusArray, 0); LogStmtError(result, stmt, L"SQLSetStmtAttr"); } //============================================================================ void SqlConnBindParameter ( SqlStmt * stmt, SQLINTEGER inputOutputType, SQLINTEGER valueType, SQLINTEGER parameterType, SQLUINTEGER columnSize, SQLINTEGER decimalDigits, SQLPOINTER parameterValuePtr, SQLINTEGER bufferLength, SQLINTEGER * indPtr ) { ASSERT(!stmt->prepare); int result = SQLBindParameter( stmt->hstmt, (SQLUSMALLINT) ++stmt->bindParam, (SQLSMALLINT) inputOutputType, (SQLSMALLINT) valueType, (SQLSMALLINT) parameterType, columnSize, (SQLSMALLINT) decimalDigits, parameterValuePtr, bufferLength, indPtr ); LogStmtError(result, stmt, L"SQLBindParameter"); } //============================================================================ void SqlConnBindParameterInt ( SqlStmt * stmt, SQLINTEGER inputOutputType, SQLINTEGER * parameterValuePtr, SQLINTEGER * indPtr ) { SqlConnBindParameter( stmt, inputOutputType, SQL_C_LONG, SQL_INTEGER, 0, 0, parameterValuePtr, 0, indPtr ); } //============================================================================ void SqlConnBindParameterUInt ( SqlStmt * stmt, SQLINTEGER inputOutputType, SQLUINTEGER * parameterValuePtr, SQLINTEGER * indPtr ) { SqlConnBindParameter( stmt, inputOutputType, SQL_C_ULONG, SQL_INTEGER, 0, 0, parameterValuePtr, 0, indPtr ); } //============================================================================ void SqlConnBindParameterString ( SqlStmt * stmt, SQLINTEGER inputOutputType, SQLUINTEGER columnSize, // bytes, NOT chars SQLWCHAR * parameterValuePtr, SQLINTEGER * indPtr ) { SqlConnBindParameter( stmt, inputOutputType, SQL_C_WCHAR, SQL_WVARCHAR, columnSize, 0, parameterValuePtr, inputOutputType == SQL_PARAM_OUTPUT ? columnSize : 0, indPtr ); } //============================================================================ void SqlConnBindParameterStringA ( SqlStmt * stmt, SQLINTEGER inputOutputType, SQLUINTEGER columnSize, // bytes, NOT chars SQLCHAR * parameterValuePtr, SQLINTEGER * indPtr ) { SqlConnBindParameter( stmt, inputOutputType, SQL_C_CHAR, SQL_VARCHAR, columnSize, 0, parameterValuePtr, inputOutputType == SQL_PARAM_OUTPUT ? columnSize : 0, indPtr ); } //============================================================================ int SqlConnExecDirect ( SqlStmt * stmt, const wchar string[] ) { #ifdef LOG_SQL_STMTS LogMsg(kLogDebug, L"SQL: %s", string); #endif StrCopy(stmt->debug, string, arrsize(stmt->debug)); int result = SQLExecDirectW(stmt->hstmt, const_cast(string), SQL_NTS); LogStmtError(result, stmt, L"SqlExecute"); return result; } //============================================================================ int SqlConnPrepare ( SqlStmt * stmt, const wchar string[] ) { // Using {call ...} or {?= call ...} with SqlPrepare doesn't allow // the ODBC expression parser to perform escape sequence fixup for // return value and output parameters; to call stored procedures, // use SqlConnExecDirect instead #ifdef HS_DEBUGGING ASSERT(string[0] != L'{'); #endif StrCopy(stmt->debug, string, arrsize(stmt->debug)); int result = SQLPrepareW(stmt->hstmt, const_cast(string), SQL_NTS); LogStmtError(result, stmt, L"SqlPrepare"); return result; } //============================================================================ int SqlConnExecute (SqlStmt * stmt) { int result = SQLExecute(stmt->hstmt); LogStmtError(result, stmt, L"SQLExecute"); return result; } //============================================================================ int SqlConnFetch (SqlStmt * stmt) { int result = SQLFetch(stmt->hstmt); LogStmtError(result, stmt, L"SQLFetch"); return result; } //============================================================================ int SqlConnMoreResults (SqlStmt * stmt) { int result = SQLMoreResults(stmt->hstmt); LogStmtError(result, stmt, L"SQLMoreResults"); return result; } //============================================================================ int SqlConnParamData ( SqlStmt * stmt, SQLPOINTER * put ) { int result = SQLParamData(stmt->hstmt, put); LogStmtError(result, stmt, L"SQLParamData"); return result; } //============================================================================ bool SqlConnPutData ( SqlStmt * stmt, SQLPOINTER data, SQLINTEGER bytes ) { int result = SQLPutData(stmt->hstmt, data, bytes); LogStmtError(result, stmt, L"SQLPutData"); return SQL_SUCCEEDED(result); } //============================================================================ int SqlConnGetData ( SqlStmt * stmt, SQLINTEGER columnNumber, SQLINTEGER targetType, SQLPOINTER targetValue, SQLINTEGER bufferLength, SQLINTEGER * indPtr ) { int result = SQLGetData( stmt->hstmt, (SQLUSMALLINT) columnNumber, (SQLSMALLINT) targetType, targetValue, bufferLength, indPtr ); LogStmtError(result, stmt, L"SQLGetData"); return result; } //============================================================================ void SqlConnCloseCursor (SqlStmt * stmt) { int result = SQLFreeStmt(stmt->hstmt, SQL_CLOSE); LogStmtError(result, stmt, L"SQLFreeStmt"); } //============================================================================ int SqlConnNumResultCols ( SqlStmt * stmt ) { // @@@ TODO: Ensure that statement has been executed SQLSMALLINT colCount = 0; int result = SQLNumResultCols( stmt->hstmt, &colCount ); LogStmtError(result, stmt, L"SQLNumResultCols"); return colCount; } //============================================================================ int SqlConnDescribeCol ( SqlStmt * stmt, SQLSMALLINT columnNumber, SQLWCHAR * columnName, SQLSMALLINT bufferLength, SQLSMALLINT * nameLengthPtr, SQLSMALLINT * dataTypePtr, SQLUINTEGER * columnSizePtr, SQLSMALLINT * decimalDigitsPtr, SQLSMALLINT * nullablePtr ) { int result = SQLDescribeColW( stmt->hstmt, columnNumber, columnName, bufferLength, nameLengthPtr, dataTypePtr, columnSizePtr, decimalDigitsPtr, nullablePtr ); LogStmtError(result, stmt, L"SQLDescribeCol"); return result; } //============================================================================ unsigned SqlConnGetPerf (unsigned id) { ASSERT(id < arrsize(s_perf)); return (unsigned) s_perf[id]; }