2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
24 * Storage Service using ODBC.
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
34 # define _CRT_NONSTDC_NO_DEPRECATE 1
35 # define _CRT_SECURE_NO_DEPRECATE 1
39 # define ODBCSTORE_EXPORTS __declspec(dllexport)
41 # define ODBCSTORE_EXPORTS
44 #include <xmltooling/logging.h>
45 #include <xmltooling/unicode.h>
46 #include <xmltooling/XMLToolingConfig.h>
47 #include <xmltooling/util/NDC.h>
48 #include <xmltooling/util/StorageService.h>
49 #include <xmltooling/util/Threads.h>
50 #include <xmltooling/util/XMLHelper.h>
51 #include <xercesc/util/XMLUniDefs.hpp>
56 #include <boost/lexical_cast.hpp>
57 #include <boost/algorithm/string.hpp>
59 using namespace xmltooling::logging;
60 using namespace xmltooling;
61 using namespace xercesc;
62 using namespace boost;
65 #define PLUGIN_VER_MAJOR 1
66 #define PLUGIN_VER_MINOR 1
68 #define LONGDATA_BUFLEN 16384
70 #define COLSIZE_CONTEXT 255
71 #define COLSIZE_ID 255
72 #define COLSIZE_STRING_VALUE 255
74 #define STRING_TABLE "strings"
75 #define TEXT_TABLE "texts"
78 CREATE TABLE version (
79 major int NOT nullptr,
83 CREATE TABLE strings (
84 context varchar(255) not null,
85 id varchar(255) not null,
86 expires datetime not null,
88 value varchar(255) not null,
89 PRIMARY KEY (context, id)
93 context varchar(255) not null,
94 id varchar(255) not null,
95 expires datetime not null,
98 PRIMARY KEY (context, id)
103 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
104 static const XMLCh isolationLevel[] = UNICODE_LITERAL_14(i,s,o,l,a,t,i,o,n,L,e,v,e,l);
105 static const XMLCh ConnectionString[] = UNICODE_LITERAL_16(C,o,n,n,e,c,t,i,o,n,S,t,r,i,n,g);
106 static const XMLCh RetryOnError[] = UNICODE_LITERAL_12(R,e,t,r,y,O,n,E,r,r,o,r);
107 static const XMLCh contextSize[] = UNICODE_LITERAL_11(c,o,n,t,e,x,t,S,i,z,e);
108 static const XMLCh keySize[] = UNICODE_LITERAL_7(k,e,y,S,i,z,e);
109 static const XMLCh stringSize[] = UNICODE_LITERAL_10(s,t,r,i,n,g,S,i,z,e);
111 // RAII for ODBC handles
113 ODBCConn(SQLHDBC conn) : handle(conn), autoCommit(true) {}
115 if (handle != SQL_NULL_HDBC) {
116 SQLRETURN sr = SQL_SUCCESS;
118 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
119 SQLDisconnect(handle);
120 SQLFreeHandle(SQL_HANDLE_DBC, handle);
121 if (!SQL_SUCCEEDED(sr))
122 throw IOException("Failed to commit connection and return to auto-commit mode.");
125 operator SQLHDBC() {return handle;}
130 class ODBCStorageService : public StorageService
133 ODBCStorageService(const DOMElement* e);
134 virtual ~ODBCStorageService();
136 const Capabilities& getCapabilities() const {
140 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
141 return createRow(STRING_TABLE, context, key, value, expiration);
143 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
144 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version);
146 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
147 return updateRow(STRING_TABLE, context, key, value, expiration, version);
149 bool deleteString(const char* context, const char* key) {
150 return deleteRow(STRING_TABLE, context, key);
153 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
154 return createRow(TEXT_TABLE, context, key, value, expiration);
156 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
157 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version);
159 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
160 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
162 bool deleteText(const char* context, const char* key) {
163 return deleteRow(TEXT_TABLE, context, key);
166 void reap(const char* context) {
167 reap(STRING_TABLE, context);
168 reap(TEXT_TABLE, context);
171 void updateContext(const char* context, time_t expiration) {
172 updateContext(STRING_TABLE, context, expiration);
173 updateContext(TEXT_TABLE, context, expiration);
176 void deleteContext(const char* context) {
177 deleteContext(STRING_TABLE, context);
178 deleteContext(TEXT_TABLE, context);
183 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
184 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version);
185 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
186 bool deleteRow(const char *table, const char* context, const char* key);
188 void reap(const char* table, const char* context);
189 void updateContext(const char* table, const char* context, time_t expiration);
190 void deleteContext(const char* table, const char* context);
193 SQLHSTMT getHSTMT(SQLHDBC);
194 pair<SQLINTEGER,SQLINTEGER> getVersion(SQLHDBC);
195 pair<bool,bool> log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=nullptr);
197 static void* cleanup_fn(void*);
202 int m_cleanupInterval;
203 scoped_ptr<CondWait> shutdown_wait;
204 Thread* cleanup_thread;
211 vector<SQLINTEGER> m_retries;
214 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
216 return new ODBCStorageService(e);
219 // convert SQL timestamp to time_t
220 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
224 t.tm_sec=expires.second;
225 t.tm_min=expires.minute;
226 t.tm_hour=expires.hour;
227 t.tm_mday=expires.day;
228 t.tm_mon=expires.month-1;
229 t.tm_year=expires.year-1900;
231 #if defined(HAVE_TIMEGM)
234 ret = mktime(&t) - timezone;
239 // conver time_t to SQL string
240 void timestampFromTime(time_t t, char* ret)
244 struct tm* ptime=gmtime_r(&t,&res);
246 struct tm* ptime=gmtime(&t);
248 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
255 SQLString(const char* src) : m_src(src) {
256 if (strchr(src, '\'')) {
258 replace_all(m_copy, "'", "''");
262 operator const char*() const {
266 const char* tostr() const {
267 return m_copy.empty() ? m_src : m_copy.c_str();
272 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
273 m_caps(XMLHelper::getAttrInt(e, 255, contextSize), XMLHelper::getAttrInt(e, 255, keySize), XMLHelper::getAttrInt(e, 255, stringSize)),
274 m_cleanupInterval(XMLHelper::getAttrInt(e, 900, cleanupInterval)),
275 cleanup_thread(nullptr), shutdown(false), m_henv(SQL_NULL_HENV), m_isolation(SQL_TXN_SERIALIZABLE), m_wideVersion(false)
278 xmltooling::NDC ndc("ODBCStorageService");
280 string iso(XMLHelper::getAttrString(e, "SERIALIZABLE", isolationLevel));
281 if (iso == "SERIALIZABLE")
282 m_isolation = SQL_TXN_SERIALIZABLE;
283 else if (iso == "REPEATABLE_READ")
284 m_isolation = SQL_TXN_REPEATABLE_READ;
285 else if (iso == "READ_COMMITTED")
286 m_isolation = SQL_TXN_READ_COMMITTED;
287 else if (iso == "READ_UNCOMMITTED")
288 m_isolation = SQL_TXN_READ_UNCOMMITTED;
290 throw XMLToolingException("Unknown transaction isolationLevel property.");
292 if (m_henv == SQL_NULL_HENV) {
293 // Enable connection pooling.
294 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
296 // Allocate the environment.
297 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
298 throw XMLToolingException("ODBC failed to initialize.");
301 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
303 m_log.info("ODBC initialized");
306 // Grab connection string from the configuration.
307 e = e ? XMLHelper::getFirstChildElement(e, ConnectionString) : nullptr;
308 auto_ptr_char arg(e ? e->getTextContent() : nullptr);
309 if (!arg.get() || !*arg.get()) {
310 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
311 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
313 m_connstring = arg.get();
315 // Connect and check version.
316 ODBCConn conn(getHDBC());
317 pair<SQLINTEGER,SQLINTEGER> v = getVersion(conn);
319 // Make sure we've got the right version.
320 if (v.first != PLUGIN_VER_MAJOR) {
321 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
322 m_log.crit("unknown database version: %d.%d", v.first, v.second);
323 throw XMLToolingException("Unknown database version for ODBC StorageService.");
326 if (v.first > 1 || v.second > 0) {
327 m_log.info("using 32-bit int type for version fields in tables");
328 m_wideVersion = true;
331 // Load any retry errors to check.
332 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
334 if (e->hasChildNodes()) {
335 m_retries.push_back(XMLString::parseInt(e->getTextContent()));
336 m_log.info("will retry operations when native ODBC error (%ld) is returned", m_retries.back());
338 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
341 // Initialize the cleanup thread
342 shutdown_wait.reset(CondWait::create());
343 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
346 ODBCStorageService::~ODBCStorageService()
349 shutdown_wait->signal();
350 cleanup_thread->join(nullptr);
351 if (m_henv != SQL_NULL_HANDLE)
352 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
355 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
364 pair<bool,bool> res = make_pair(false,false);
366 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
367 if (SQL_SUCCEEDED(ret)) {
368 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
369 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
370 res.first = (*n == native);
371 if (checkfor && !strcmp(checkfor, (const char*)state))
374 } while(SQL_SUCCEEDED(ret));
378 SQLHDBC ODBCStorageService::getHDBC()
381 xmltooling::NDC ndc("getHDBC");
385 SQLHDBC handle = SQL_NULL_HDBC;
386 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
387 if (!SQL_SUCCEEDED(sr) || handle == SQL_NULL_HDBC) {
388 m_log.error("failed to allocate connection handle");
389 log_error(m_henv, SQL_HANDLE_ENV);
390 throw IOException("ODBC StorageService failed to allocate a connection handle.");
393 sr = SQLDriverConnect(handle,nullptr,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),nullptr,0,nullptr,SQL_DRIVER_NOPROMPT);
394 if (!SQL_SUCCEEDED(sr)) {
395 m_log.error("failed to connect to database");
396 log_error(handle, SQL_HANDLE_DBC);
397 SQLFreeHandle(SQL_HANDLE_DBC, handle);
398 throw IOException("ODBC StorageService failed to connect to database.");
401 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, 0);
402 if (!SQL_SUCCEEDED(sr)) {
403 SQLDisconnect(handle);
404 SQLFreeHandle(SQL_HANDLE_DBC, handle);
405 throw IOException("ODBC StorageService failed to set transaction isolation level.");
411 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
413 SQLHSTMT hstmt = SQL_NULL_HSTMT;
414 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
415 if (!SQL_SUCCEEDED(sr) || hstmt == SQL_NULL_HSTMT) {
416 m_log.error("failed to allocate statement handle");
417 log_error(conn, SQL_HANDLE_DBC);
418 throw IOException("ODBC StorageService failed to allocate a statement handle.");
423 pair<SQLINTEGER,SQLINTEGER> ODBCStorageService::getVersion(SQLHDBC conn)
425 // Grab the version number from the database.
426 SQLHSTMT stmt = getHSTMT(conn);
428 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
429 if (!SQL_SUCCEEDED(sr)) {
430 m_log.error("failed to read version from database");
431 log_error(stmt, SQL_HANDLE_STMT);
432 throw IOException("ODBC StorageService failed to read version from database.");
437 SQLBindCol(stmt, 1, SQL_C_SLONG, &major, 0, nullptr);
438 SQLBindCol(stmt, 2, SQL_C_SLONG, &minor, 0, nullptr);
440 if ((sr = SQLFetch(stmt)) != SQL_NO_DATA)
441 return make_pair(major,minor);
443 m_log.error("no rows returned in version query");
444 throw IOException("ODBC StorageService failed to read version from database.");
447 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
450 xmltooling::NDC ndc("createRow");
454 timestampFromTime(expiration, timebuf);
456 // Get statement handle.
457 ODBCConn conn(getHDBC());
458 SQLHSTMT stmt = getHSTMT(conn);
460 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
462 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
463 if (!SQL_SUCCEEDED(sr)) {
464 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
465 log_error(stmt, SQL_HANDLE_STMT);
466 throw IOException("ODBC StorageService failed to insert record.");
468 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
470 SQLLEN b_ind = SQL_NTS;
471 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
472 if (!SQL_SUCCEEDED(sr)) {
473 m_log.error("SQLBindParam failed (context = %s)", context);
474 log_error(stmt, SQL_HANDLE_STMT);
475 throw IOException("ODBC StorageService failed to insert record.");
477 m_log.debug("SQLBindParam succeeded (context = %s)", context);
479 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
480 if (!SQL_SUCCEEDED(sr)) {
481 m_log.error("SQLBindParam failed (key = %s)", key);
482 log_error(stmt, SQL_HANDLE_STMT);
483 throw IOException("ODBC StorageService failed to insert record.");
485 m_log.debug("SQLBindParam succeeded (key = %s)", key);
487 if (strcmp(table, TEXT_TABLE)==0)
488 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
490 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
491 if (!SQL_SUCCEEDED(sr)) {
492 m_log.error("SQLBindParam failed (value = %s)", value);
493 log_error(stmt, SQL_HANDLE_STMT);
494 throw IOException("ODBC StorageService failed to insert record.");
496 m_log.debug("SQLBindParam succeeded (value = %s)", value);
499 pair<bool,bool> logres;
501 logres = make_pair(false,false);
503 sr = SQLExecute(stmt);
504 if (SQL_SUCCEEDED(sr)) {
505 m_log.debug("SQLExecute of insert succeeded");
508 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
509 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
511 return false; // supposedly integrity violation?
512 } while (attempts && logres.first);
514 throw IOException("ODBC StorageService failed to insert record.");
517 int ODBCStorageService::readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
520 xmltooling::NDC ndc("readRow");
523 // Get statement handle.
524 ODBCConn conn(getHDBC());
525 SQLHSTMT stmt = getHSTMT(conn);
527 // Prepare and exectute select statement.
529 timestampFromTime(time(nullptr), timebuf);
530 SQLString scontext(context);
532 string q("SELECT version");
537 q = q + ",CASE version WHEN " + lexical_cast<string>(version) + " THEN null ELSE value END";
539 q = q + " FROM " + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
540 if (m_log.isDebugEnabled())
541 m_log.debug("SQL: %s", q.c_str());
543 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
544 if (!SQL_SUCCEEDED(sr)) {
545 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
546 log_error(stmt, SQL_HANDLE_STMT);
547 throw IOException("ODBC StorageService search failed.");
552 SQL_TIMESTAMP_STRUCT expiration;
555 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
557 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
559 SQLBindCol(stmt, 2, SQL_C_TYPE_TIMESTAMP, &expiration, 0, nullptr);
561 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
562 if (m_log.isDebugEnabled())
563 m_log.debug("search returned no data (t=%s, c=%s, k=%s)", table, context, key);
568 *pexpiration = timeFromTimestamp(expiration);
570 if (version == (m_wideVersion ? widever : ver)) {
571 if (m_log.isDebugEnabled())
572 m_log.debug("versioned search detected no change (t=%s, c=%s, k=%s)", table, context, key);
573 return version; // nothing's changed, so just echo back the version
578 SQLCHAR buf[LONGDATA_BUFLEN];
579 while ((sr = SQLGetData(stmt, (pexpiration ? 3 : 2), SQL_C_CHAR, buf, sizeof(buf), &len)) != SQL_NO_DATA) {
580 if (!SQL_SUCCEEDED(sr)) {
581 m_log.error("error while reading text field from result set");
582 log_error(stmt, SQL_HANDLE_STMT);
583 throw IOException("ODBC StorageService search failed to read data from result set.");
585 pvalue->append((char*)buf);
589 return (m_wideVersion ? widever : ver);
592 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
595 xmltooling::NDC ndc("updateRow");
598 if (!value && !expiration)
599 throw IOException("ODBC StorageService given invalid update instructions.");
601 // Get statement handle. Disable auto-commit mode to wrap select + update.
602 ODBCConn conn(getHDBC());
603 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
604 if (!SQL_SUCCEEDED(sr))
605 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
606 conn.autoCommit = false;
607 SQLHSTMT stmt = getHSTMT(conn);
609 // First, fetch the current version for later, which also ensures the record still exists.
611 timestampFromTime(time(nullptr), timebuf);
612 SQLString scontext(context);
614 string q("SELECT version FROM ");
615 q = q + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
617 m_log.debug("SQL: %s", q.c_str());
619 sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
620 if (!SQL_SUCCEEDED(sr)) {
621 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
622 log_error(stmt, SQL_HANDLE_STMT);
623 throw IOException("ODBC StorageService search failed.");
629 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
631 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
632 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
637 if (version > 0 && version != (m_wideVersion ? widever : ver)) {
640 else if ((m_wideVersion && widever == INT_MAX) || (!m_wideVersion && ver == 32767)) {
641 m_log.error("record version overflow (t=%s, c=%s, k=%s)", table, context, key);
642 throw IOException("Version overflow, record in ODBC StorageService could not be updated.");
645 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
646 stmt = getHSTMT(conn);
648 // Prepare and exectute update statement.
649 q = string("UPDATE ") + table + " SET ";
652 q = q + "value=?, version=version+1";
655 timestampFromTime(expiration, timebuf);
658 q = q + "expires = " + timebuf;
661 q = q + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
663 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
664 if (!SQL_SUCCEEDED(sr)) {
665 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
666 log_error(stmt, SQL_HANDLE_STMT);
667 throw IOException("ODBC StorageService failed to update record.");
669 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
671 SQLLEN b_ind = SQL_NTS;
673 if (strcmp(table, TEXT_TABLE)==0)
674 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
676 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
677 if (!SQL_SUCCEEDED(sr)) {
678 m_log.error("SQLBindParam failed (value = %s)", value);
679 log_error(stmt, SQL_HANDLE_STMT);
680 throw IOException("ODBC StorageService failed to update record.");
682 m_log.debug("SQLBindParam succeeded (value = %s)", value);
686 pair<bool,bool> logres;
688 logres = make_pair(false,false);
690 sr = SQLExecute(stmt);
691 if (sr == SQL_NO_DATA)
692 return 0; // went missing?
693 else if (SQL_SUCCEEDED(sr)) {
694 m_log.debug("SQLExecute of update succeeded");
695 return (m_wideVersion ? widever : ver) + 1;
698 m_log.error("update of record failed (t=%s, c=%s, k=%s)", table, context, key);
699 logres = log_error(stmt, SQL_HANDLE_STMT);
700 } while (attempts && logres.first);
702 throw IOException("ODBC StorageService failed to update record.");
705 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
708 xmltooling::NDC ndc("deleteRow");
711 // Get statement handle.
712 ODBCConn conn(getHDBC());
713 SQLHSTMT stmt = getHSTMT(conn);
715 // Prepare and execute delete statement.
716 SQLString scontext(context);
718 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
719 m_log.debug("SQL: %s", q.c_str());
721 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
722 if (sr == SQL_NO_DATA)
724 else if (!SQL_SUCCEEDED(sr)) {
725 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
726 log_error(stmt, SQL_HANDLE_STMT);
727 throw IOException("ODBC StorageService failed to delete record.");
734 void ODBCStorageService::cleanup()
737 xmltooling::NDC ndc("cleanup");
740 scoped_ptr<Mutex> mutex(Mutex::create());
744 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
747 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
753 catch (std::exception& ex) {
754 m_log.error("cleanup thread swallowed exception: %s", ex.what());
758 m_log.info("cleanup thread exiting...");
761 Thread::exit(nullptr);
764 void* ODBCStorageService::cleanup_fn(void* cache_p)
766 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
769 // First, let's block all signals
770 Thread::mask_all_signals();
773 // Now run the cleanup process.
778 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
781 xmltooling::NDC ndc("updateContext");
784 // Get statement handle.
785 ODBCConn conn(getHDBC());
786 SQLHSTMT stmt = getHSTMT(conn);
789 timestampFromTime(expiration, timebuf);
792 timestampFromTime(time(nullptr), nowbuf);
794 SQLString scontext(context);
795 string q = string("UPDATE ") + table + " SET expires = " + timebuf + " WHERE context='" + scontext.tostr() + "' AND expires > " + nowbuf;
797 m_log.debug("SQL: %s", q.c_str());
799 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
800 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
801 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
802 log_error(stmt, SQL_HANDLE_STMT);
803 throw IOException("ODBC StorageService failed to update context expiration.");
807 void ODBCStorageService::reap(const char *table, const char* context)
810 xmltooling::NDC ndc("reap");
813 // Get statement handle.
814 ODBCConn conn(getHDBC());
815 SQLHSTMT stmt = getHSTMT(conn);
817 // Prepare and execute delete statement.
819 timestampFromTime(time(nullptr), nowbuf);
822 SQLString scontext(context);
823 q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND expires <= " + nowbuf;
826 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
828 m_log.debug("SQL: %s", q.c_str());
830 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
831 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
832 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
833 log_error(stmt, SQL_HANDLE_STMT);
834 throw IOException("ODBC StorageService failed to purge expired records.");
838 void ODBCStorageService::deleteContext(const char *table, const char* context)
841 xmltooling::NDC ndc("deleteContext");
844 // Get statement handle.
845 ODBCConn conn(getHDBC());
846 SQLHSTMT stmt = getHSTMT(conn);
848 // Prepare and execute delete statement.
849 SQLString scontext(context);
850 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "'";
851 m_log.debug("SQL: %s", q.c_str());
853 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
854 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
855 m_log.error("error deleting context (t=%s, c=%s)", table, context);
856 log_error(stmt, SQL_HANDLE_STMT);
857 throw IOException("ODBC StorageService failed to delete context.");
861 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
863 // Register this SS type
864 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
868 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
870 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");