|
|
|
/*==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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
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);
|
|
|
|
uint32_t 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;
|
|
|
|
uint8_t * data;
|
|
|
|
wchar_t debug[128];
|
|
|
|
|
|
|
|
SqlStmt (SqlConn * conn);
|
|
|
|
~SqlStmt ();
|
|
|
|
SqlStmt & operator= (const SqlStmt &); // not impl.
|
|
|
|
bool Initialize (const wchar_t 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_t * connectStr;
|
|
|
|
|
|
|
|
LISTDECL(
|
|
|
|
SqlStmt,
|
|
|
|
listLink
|
|
|
|
) stmts;
|
|
|
|
|
|
|
|
HASHTABLEDECL(
|
|
|
|
SqlStmt,
|
|
|
|
SqlStmtHash,
|
|
|
|
hashLink
|
|
|
|
) freeStmts;
|
|
|
|
|
|
|
|
SqlConn (
|
|
|
|
const wchar_t 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_t function[],
|
|
|
|
const wchar_t 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_t 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 uint32_t SqlStmtHash::GetHash () const {
|
|
|
|
return (uint32_t) 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_t 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_t 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());
|
|
|
|
delete 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) {
|
|
|
|
delete 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) {
|
|
|
|
delete 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_t 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete 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) {
|
|
|
|
delete stmt;
|
|
|
|
conn->AsyncGrowConnections_CS();
|
|
|
|
}
|
|
|
|
else if (dead) {
|
|
|
|
delete 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,
|
|
|
|
uint8_t ** 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(uint8_t) 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_t 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<wchar_t *>(string), SQL_NTS);
|
|
|
|
LogStmtError(result, stmt, L"SqlExecute");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
int SqlConnPrepare (
|
|
|
|
SqlStmt * stmt,
|
|
|
|
const wchar_t 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<wchar_t *>(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];
|
|
|
|
}
|