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;
236 ret = mktime(&t) - timezone;
241 // conver time_t to SQL string
242 void timestampFromTime(time_t t, char* ret)
246 struct tm* ptime=gmtime_r(&t,&res);
248 struct tm* ptime=gmtime(&t);
250 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
257 SQLString(const char* src) : m_src(src) {
258 if (strchr(src, '\'')) {
260 replace_all(m_copy, "'", "''");
264 operator const char*() const {
268 const char* tostr() const {
269 return m_copy.empty() ? m_src : m_copy.c_str();
274 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
275 m_caps(XMLHelper::getAttrInt(e, 255, contextSize), XMLHelper::getAttrInt(e, 255, keySize), XMLHelper::getAttrInt(e, 255, stringSize)),
276 m_cleanupInterval(XMLHelper::getAttrInt(e, 900, cleanupInterval)),
277 cleanup_thread(nullptr), shutdown(false), m_henv(SQL_NULL_HENV), m_isolation(SQL_TXN_SERIALIZABLE), m_wideVersion(false)
280 xmltooling::NDC ndc("ODBCStorageService");
282 string iso(XMLHelper::getAttrString(e, "SERIALIZABLE", isolationLevel));
283 if (iso == "SERIALIZABLE")
284 m_isolation = SQL_TXN_SERIALIZABLE;
285 else if (iso == "REPEATABLE_READ")
286 m_isolation = SQL_TXN_REPEATABLE_READ;
287 else if (iso == "READ_COMMITTED")
288 m_isolation = SQL_TXN_READ_COMMITTED;
289 else if (iso == "READ_UNCOMMITTED")
290 m_isolation = SQL_TXN_READ_UNCOMMITTED;
292 throw XMLToolingException("Unknown transaction isolationLevel property.");
294 if (m_henv == SQL_NULL_HENV) {
295 // Enable connection pooling.
296 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
298 // Allocate the environment.
299 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
300 throw XMLToolingException("ODBC failed to initialize.");
303 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
305 m_log.info("ODBC initialized");
308 // Grab connection string from the configuration.
309 e = e ? XMLHelper::getFirstChildElement(e, ConnectionString) : nullptr;
310 auto_ptr_char arg(e ? e->getTextContent() : nullptr);
311 if (!arg.get() || !*arg.get()) {
312 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
313 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
315 m_connstring = arg.get();
317 // Connect and check version.
318 ODBCConn conn(getHDBC());
319 pair<SQLINTEGER,SQLINTEGER> v = getVersion(conn);
321 // Make sure we've got the right version.
322 if (v.first != PLUGIN_VER_MAJOR) {
323 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
324 m_log.crit("unknown database version: %d.%d", v.first, v.second);
325 throw XMLToolingException("Unknown database version for ODBC StorageService.");
328 if (v.first > 1 || v.second > 0) {
329 m_log.info("using 32-bit int type for version fields in tables");
330 m_wideVersion = true;
333 // Load any retry errors to check.
334 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
336 if (e->hasChildNodes()) {
338 int code = XMLString::parseInt(e->getTextContent());
339 m_retries.push_back(code);
340 m_log.info("will retry operations when native ODBC error (%d) is returned", code);
342 catch (XMLException&) {
343 m_log.error("skipping non-numeric ODBC retry code");
346 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
349 if (m_cleanupInterval > 0) {
350 // Initialize the cleanup thread
351 shutdown_wait.reset(CondWait::create());
352 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
355 m_log.info("no cleanup interval configured, no cleanup thread will be started");
359 ODBCStorageService::~ODBCStorageService()
362 if (shutdown_wait.get()) {
363 shutdown_wait->signal();
365 if (cleanup_thread) {
366 cleanup_thread->join(nullptr);
368 if (m_henv != SQL_NULL_HANDLE) {
369 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
373 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
382 pair<bool,bool> res = make_pair(false,false);
384 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
385 if (SQL_SUCCEEDED(ret)) {
386 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
387 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
388 res.first = (*n == native);
389 if (checkfor && !strcmp(checkfor, (const char*)state))
392 } while(SQL_SUCCEEDED(ret));
396 SQLHDBC ODBCStorageService::getHDBC()
399 xmltooling::NDC ndc("getHDBC");
403 SQLHDBC handle = SQL_NULL_HDBC;
404 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
405 if (!SQL_SUCCEEDED(sr) || handle == SQL_NULL_HDBC) {
406 m_log.error("failed to allocate connection handle");
407 log_error(m_henv, SQL_HANDLE_ENV);
408 throw IOException("ODBC StorageService failed to allocate a connection handle.");
411 sr = SQLDriverConnect(handle,nullptr,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),nullptr,0,nullptr,SQL_DRIVER_NOPROMPT);
412 if (!SQL_SUCCEEDED(sr)) {
413 m_log.error("failed to connect to database");
414 log_error(handle, SQL_HANDLE_DBC);
415 SQLFreeHandle(SQL_HANDLE_DBC, handle);
416 throw IOException("ODBC StorageService failed to connect to database.");
419 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, 0);
420 if (!SQL_SUCCEEDED(sr)) {
421 SQLDisconnect(handle);
422 SQLFreeHandle(SQL_HANDLE_DBC, handle);
423 throw IOException("ODBC StorageService failed to set transaction isolation level.");
429 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
431 SQLHSTMT hstmt = SQL_NULL_HSTMT;
432 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
433 if (!SQL_SUCCEEDED(sr) || hstmt == SQL_NULL_HSTMT) {
434 m_log.error("failed to allocate statement handle");
435 log_error(conn, SQL_HANDLE_DBC);
436 throw IOException("ODBC StorageService failed to allocate a statement handle.");
441 pair<SQLINTEGER,SQLINTEGER> ODBCStorageService::getVersion(SQLHDBC conn)
443 // Grab the version number from the database.
444 SQLHSTMT stmt = getHSTMT(conn);
446 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
447 if (!SQL_SUCCEEDED(sr)) {
448 m_log.error("failed to read version from database");
449 log_error(stmt, SQL_HANDLE_STMT);
450 throw IOException("ODBC StorageService failed to read version from database.");
455 SQLBindCol(stmt, 1, SQL_C_SLONG, &major, 0, nullptr);
456 SQLBindCol(stmt, 2, SQL_C_SLONG, &minor, 0, nullptr);
458 if ((sr = SQLFetch(stmt)) != SQL_NO_DATA)
459 return make_pair(major,minor);
461 m_log.error("no rows returned in version query");
462 throw IOException("ODBC StorageService failed to read version from database.");
465 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
468 xmltooling::NDC ndc("createRow");
472 timestampFromTime(expiration, timebuf);
474 // Get statement handle.
475 ODBCConn conn(getHDBC());
476 SQLHSTMT stmt = getHSTMT(conn);
478 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
480 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
481 if (!SQL_SUCCEEDED(sr)) {
482 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
483 log_error(stmt, SQL_HANDLE_STMT);
484 throw IOException("ODBC StorageService failed to insert record.");
486 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
488 SQLLEN b_ind = SQL_NTS;
489 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
490 if (!SQL_SUCCEEDED(sr)) {
491 m_log.error("SQLBindParam failed (context = %s)", context);
492 log_error(stmt, SQL_HANDLE_STMT);
493 throw IOException("ODBC StorageService failed to insert record.");
495 m_log.debug("SQLBindParam succeeded (context = %s)", context);
497 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
498 if (!SQL_SUCCEEDED(sr)) {
499 m_log.error("SQLBindParam failed (key = %s)", key);
500 log_error(stmt, SQL_HANDLE_STMT);
501 throw IOException("ODBC StorageService failed to insert record.");
503 m_log.debug("SQLBindParam succeeded (key = %s)", key);
505 if (strcmp(table, TEXT_TABLE)==0)
506 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
508 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
509 if (!SQL_SUCCEEDED(sr)) {
510 m_log.error("SQLBindParam failed (value = %s)", value);
511 log_error(stmt, SQL_HANDLE_STMT);
512 throw IOException("ODBC StorageService failed to insert record.");
514 m_log.debug("SQLBindParam succeeded (value = %s)", value);
517 pair<bool,bool> logres;
519 logres = make_pair(false,false);
521 sr = SQLExecute(stmt);
522 if (SQL_SUCCEEDED(sr)) {
523 m_log.debug("SQLExecute of insert succeeded");
526 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
527 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
529 // Supposedly integrity violation.
530 // Try and delete any expired record still hanging around until the final attempt.
532 reap(table, context);
533 logres.first = true; // force it to treat as a retryable error
538 } while (attempts && logres.first);
540 throw IOException("ODBC StorageService failed to insert record.");
543 int ODBCStorageService::readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
546 xmltooling::NDC ndc("readRow");
549 // Get statement handle.
550 ODBCConn conn(getHDBC());
551 SQLHSTMT stmt = getHSTMT(conn);
553 // Prepare and exectute select statement.
555 timestampFromTime(time(nullptr), timebuf);
556 SQLString scontext(context);
558 string q("SELECT version");
563 q = q + ",CASE version WHEN " + lexical_cast<string>(version) + " THEN null ELSE value END";
565 q = q + " FROM " + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
566 if (m_log.isDebugEnabled())
567 m_log.debug("SQL: %s", q.c_str());
569 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
570 if (!SQL_SUCCEEDED(sr)) {
571 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
572 log_error(stmt, SQL_HANDLE_STMT);
573 throw IOException("ODBC StorageService search failed.");
578 SQL_TIMESTAMP_STRUCT expiration;
581 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
583 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
585 SQLBindCol(stmt, 2, SQL_C_TYPE_TIMESTAMP, &expiration, 0, nullptr);
587 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
588 if (m_log.isDebugEnabled())
589 m_log.debug("search returned no data (t=%s, c=%s, k=%s)", table, context, key);
594 *pexpiration = timeFromTimestamp(expiration);
596 if (version == (m_wideVersion ? widever : ver)) {
597 if (m_log.isDebugEnabled())
598 m_log.debug("versioned search detected no change (t=%s, c=%s, k=%s)", table, context, key);
599 return version; // nothing's changed, so just echo back the version
604 SQLCHAR buf[LONGDATA_BUFLEN];
605 while ((sr = SQLGetData(stmt, (pexpiration ? 3 : 2), SQL_C_CHAR, buf, sizeof(buf), &len)) != SQL_NO_DATA) {
606 if (!SQL_SUCCEEDED(sr)) {
607 m_log.error("error while reading text field from result set");
608 log_error(stmt, SQL_HANDLE_STMT);
609 throw IOException("ODBC StorageService search failed to read data from result set.");
611 pvalue->append((char*)buf);
615 return (m_wideVersion ? widever : ver);
618 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
621 xmltooling::NDC ndc("updateRow");
624 if (!value && !expiration)
625 throw IOException("ODBC StorageService given invalid update instructions.");
627 // Get statement handle. Disable auto-commit mode to wrap select + update.
628 ODBCConn conn(getHDBC());
629 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
630 if (!SQL_SUCCEEDED(sr))
631 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
632 conn.autoCommit = false;
633 SQLHSTMT stmt = getHSTMT(conn);
635 // First, fetch the current version for later, which also ensures the record still exists.
637 timestampFromTime(time(nullptr), timebuf);
638 SQLString scontext(context);
640 string q("SELECT version FROM ");
641 q = q + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
643 m_log.debug("SQL: %s", q.c_str());
645 sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
646 if (!SQL_SUCCEEDED(sr)) {
647 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
648 log_error(stmt, SQL_HANDLE_STMT);
649 throw IOException("ODBC StorageService search failed.");
655 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
657 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
658 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
663 if (version > 0 && version != (m_wideVersion ? widever : ver)) {
666 else if ((m_wideVersion && widever == INT_MAX) || (!m_wideVersion && ver == 32767)) {
667 m_log.error("record version overflow (t=%s, c=%s, k=%s)", table, context, key);
668 throw IOException("Version overflow, record in ODBC StorageService could not be updated.");
671 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
672 stmt = getHSTMT(conn);
674 // Prepare and exectute update statement.
675 q = string("UPDATE ") + table + " SET ";
678 q = q + "value=?, version=version+1";
681 timestampFromTime(expiration, timebuf);
684 q = q + "expires = " + timebuf;
687 q = q + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
689 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
690 if (!SQL_SUCCEEDED(sr)) {
691 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
692 log_error(stmt, SQL_HANDLE_STMT);
693 throw IOException("ODBC StorageService failed to update record.");
695 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
697 SQLLEN b_ind = SQL_NTS;
699 if (strcmp(table, TEXT_TABLE)==0)
700 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
702 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
703 if (!SQL_SUCCEEDED(sr)) {
704 m_log.error("SQLBindParam failed (value = %s)", value);
705 log_error(stmt, SQL_HANDLE_STMT);
706 throw IOException("ODBC StorageService failed to update record.");
708 m_log.debug("SQLBindParam succeeded (value = %s)", value);
712 pair<bool,bool> logres;
714 logres = make_pair(false,false);
716 sr = SQLExecute(stmt);
717 if (sr == SQL_NO_DATA)
718 return 0; // went missing?
719 else if (SQL_SUCCEEDED(sr)) {
720 m_log.debug("SQLExecute of update succeeded");
721 return (m_wideVersion ? widever : ver) + 1;
724 m_log.error("update of record failed (t=%s, c=%s, k=%s)", table, context, key);
725 logres = log_error(stmt, SQL_HANDLE_STMT);
726 } while (attempts && logres.first);
728 throw IOException("ODBC StorageService failed to update record.");
731 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
734 xmltooling::NDC ndc("deleteRow");
737 // Get statement handle.
738 ODBCConn conn(getHDBC());
739 SQLHSTMT stmt = getHSTMT(conn);
741 // Prepare and execute delete statement.
742 SQLString scontext(context);
744 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
745 m_log.debug("SQL: %s", q.c_str());
747 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
748 if (sr == SQL_NO_DATA)
750 else if (!SQL_SUCCEEDED(sr)) {
751 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
752 log_error(stmt, SQL_HANDLE_STMT);
753 throw IOException("ODBC StorageService failed to delete record.");
760 void ODBCStorageService::cleanup()
763 xmltooling::NDC ndc("cleanup");
766 scoped_ptr<Mutex> mutex(Mutex::create());
770 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
773 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
779 catch (std::exception& ex) {
780 m_log.error("cleanup thread swallowed exception: %s", ex.what());
784 m_log.info("cleanup thread exiting...");
787 Thread::exit(nullptr);
790 void* ODBCStorageService::cleanup_fn(void* cache_p)
792 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
795 // First, let's block all signals
796 Thread::mask_all_signals();
799 // Now run the cleanup process.
804 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
807 xmltooling::NDC ndc("updateContext");
810 // Get statement handle.
811 ODBCConn conn(getHDBC());
812 SQLHSTMT stmt = getHSTMT(conn);
815 timestampFromTime(expiration, timebuf);
818 timestampFromTime(time(nullptr), nowbuf);
820 SQLString scontext(context);
821 string q = string("UPDATE ") + table + " SET expires = " + timebuf + " WHERE context='" + scontext.tostr() + "' AND expires > " + nowbuf;
823 m_log.debug("SQL: %s", q.c_str());
825 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
826 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
827 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
828 log_error(stmt, SQL_HANDLE_STMT);
829 throw IOException("ODBC StorageService failed to update context expiration.");
833 void ODBCStorageService::reap(const char *table, const char* context)
836 xmltooling::NDC ndc("reap");
839 // Get statement handle.
840 ODBCConn conn(getHDBC());
841 SQLHSTMT stmt = getHSTMT(conn);
843 // Prepare and execute delete statement.
845 timestampFromTime(time(nullptr), nowbuf);
848 SQLString scontext(context);
849 q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND expires <= " + nowbuf;
852 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
854 m_log.debug("SQL: %s", q.c_str());
856 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
857 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
858 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
859 log_error(stmt, SQL_HANDLE_STMT);
860 throw IOException("ODBC StorageService failed to purge expired records.");
864 void ODBCStorageService::deleteContext(const char *table, const char* context)
867 xmltooling::NDC ndc("deleteContext");
870 // Get statement handle.
871 ODBCConn conn(getHDBC());
872 SQLHSTMT stmt = getHSTMT(conn);
874 // Prepare and execute delete statement.
875 SQLString scontext(context);
876 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "'";
877 m_log.debug("SQL: %s", q.c_str());
879 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
880 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
881 m_log.error("error deleting context (t=%s, c=%s)", table, context);
882 log_error(stmt, SQL_HANDLE_STMT);
883 throw IOException("ODBC StorageService failed to delete context.");
887 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
889 // Register this SS type
890 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
894 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
896 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");