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 0
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,
87 version smallint 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,
96 version smallint 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 SQLRETURN sr = SQL_SUCCESS;
117 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
118 SQLDisconnect(handle);
119 SQLFreeHandle(SQL_HANDLE_DBC,handle);
120 if (!SQL_SUCCEEDED(sr))
121 throw IOException("Failed to commit connection and return to auto-commit mode.");
123 operator SQLHDBC() {return handle;}
128 class ODBCStorageService : public StorageService
131 ODBCStorageService(const DOMElement* e);
132 virtual ~ODBCStorageService();
134 const Capabilities& getCapabilities() const {
138 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
139 return createRow(STRING_TABLE, context, key, value, expiration);
141 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
142 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version);
144 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
145 return updateRow(STRING_TABLE, context, key, value, expiration, version);
147 bool deleteString(const char* context, const char* key) {
148 return deleteRow(STRING_TABLE, context, key);
151 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
152 return createRow(TEXT_TABLE, context, key, value, expiration);
154 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
155 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version);
157 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
158 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
160 bool deleteText(const char* context, const char* key) {
161 return deleteRow(TEXT_TABLE, context, key);
164 void reap(const char* context) {
165 reap(STRING_TABLE, context);
166 reap(TEXT_TABLE, context);
169 void updateContext(const char* context, time_t expiration) {
170 updateContext(STRING_TABLE, context, expiration);
171 updateContext(TEXT_TABLE, context, expiration);
174 void deleteContext(const char* context) {
175 deleteContext(STRING_TABLE, context);
176 deleteContext(TEXT_TABLE, context);
181 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
182 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version);
183 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
184 bool deleteRow(const char *table, const char* context, const char* key);
186 void reap(const char* table, const char* context);
187 void updateContext(const char* table, const char* context, time_t expiration);
188 void deleteContext(const char* table, const char* context);
191 SQLHSTMT getHSTMT(SQLHDBC);
192 pair<SQLINTEGER,SQLINTEGER> getVersion(SQLHDBC);
193 pair<bool,bool> log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=nullptr);
195 static void* cleanup_fn(void*);
200 int m_cleanupInterval;
201 scoped_ptr<CondWait> shutdown_wait;
202 Thread* cleanup_thread;
208 vector<SQLINTEGER> m_retries;
211 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
213 return new ODBCStorageService(e);
216 // convert SQL timestamp to time_t
217 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
221 t.tm_sec=expires.second;
222 t.tm_min=expires.minute;
223 t.tm_hour=expires.hour;
224 t.tm_mday=expires.day;
225 t.tm_mon=expires.month-1;
226 t.tm_year=expires.year-1900;
228 #if defined(HAVE_TIMEGM)
231 ret = mktime(&t) - timezone;
236 // conver time_t to SQL string
237 void timestampFromTime(time_t t, char* ret)
241 struct tm* ptime=gmtime_r(&t,&res);
243 struct tm* ptime=gmtime(&t);
245 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
252 SQLString(const char* src) : m_src(src) {
253 if (strchr(src, '\'')) {
255 replace_all(m_copy, "'", "''");
259 operator const char*() const {
263 const char* tostr() const {
264 return m_copy.empty() ? m_src : m_copy.c_str();
269 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
270 m_caps(XMLHelper::getAttrInt(e, 255, contextSize), XMLHelper::getAttrInt(e, 255, keySize), XMLHelper::getAttrInt(e, 255, stringSize)),
271 m_cleanupInterval(XMLHelper::getAttrInt(e, 900, cleanupInterval)),
272 cleanup_thread(nullptr), shutdown(false), m_henv(SQL_NULL_HANDLE), m_isolation(SQL_TXN_SERIALIZABLE)
275 xmltooling::NDC ndc("ODBCStorageService");
277 string iso(XMLHelper::getAttrString(e, "SERIALIZABLE", isolationLevel));
278 if (iso == "SERIALIZABLE")
279 m_isolation = SQL_TXN_SERIALIZABLE;
280 else if (iso == "REPEATABLE_READ")
281 m_isolation = SQL_TXN_REPEATABLE_READ;
282 else if (iso == "READ_COMMITTED")
283 m_isolation = SQL_TXN_READ_COMMITTED;
284 else if (iso == "READ_UNCOMMITTED")
285 m_isolation = SQL_TXN_READ_UNCOMMITTED;
287 throw XMLToolingException("Unknown transaction isolationLevel property.");
289 if (m_henv == SQL_NULL_HANDLE) {
290 // Enable connection pooling.
291 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
293 // Allocate the environment.
294 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
295 throw XMLToolingException("ODBC failed to initialize.");
298 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
300 m_log.info("ODBC initialized");
303 // Grab connection string from the configuration.
304 e = e ? XMLHelper::getFirstChildElement(e, ConnectionString) : nullptr;
305 auto_ptr_char arg(e ? e->getTextContent() : nullptr);
306 if (!arg.get() || !*arg.get()) {
307 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
308 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
310 m_connstring = arg.get();
312 // Connect and check version.
313 ODBCConn conn(getHDBC());
314 pair<SQLINTEGER,SQLINTEGER> v = getVersion(conn);
316 // Make sure we've got the right version.
317 if (v.first != PLUGIN_VER_MAJOR) {
318 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
319 m_log.crit("unknown database version: %d.%d", v.first, v.second);
320 throw XMLToolingException("Unknown database version for ODBC StorageService.");
323 // Load any retry errors to check.
324 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
326 if (e->hasChildNodes()) {
327 m_retries.push_back(XMLString::parseInt(e->getTextContent()));
328 m_log.info("will retry operations when native ODBC error (%ld) is returned", m_retries.back());
330 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
333 // Initialize the cleanup thread
334 shutdown_wait.reset(CondWait::create());
335 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
338 ODBCStorageService::~ODBCStorageService()
341 shutdown_wait->signal();
342 cleanup_thread->join(nullptr);
343 if (m_henv != SQL_NULL_HANDLE)
344 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
347 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
356 pair<bool,bool> res = make_pair(false,false);
358 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
359 if (SQL_SUCCEEDED(ret)) {
360 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
361 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
362 res.first = (*n == native);
363 if (checkfor && !strcmp(checkfor, (const char*)state))
366 } while(SQL_SUCCEEDED(ret));
370 SQLHDBC ODBCStorageService::getHDBC()
373 xmltooling::NDC ndc("getHDBC");
378 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
379 if (!SQL_SUCCEEDED(sr)) {
380 m_log.error("failed to allocate connection handle");
381 log_error(m_henv, SQL_HANDLE_ENV);
382 throw IOException("ODBC StorageService failed to allocate a connection handle.");
385 sr=SQLDriverConnect(handle,nullptr,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),nullptr,0,nullptr,SQL_DRIVER_NOPROMPT);
386 if (!SQL_SUCCEEDED(sr)) {
387 m_log.error("failed to connect to database");
388 log_error(handle, SQL_HANDLE_DBC);
389 throw IOException("ODBC StorageService failed to connect to database.");
392 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, 0);
393 if (!SQL_SUCCEEDED(sr))
394 throw IOException("ODBC StorageService failed to set transaction isolation level.");
399 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
402 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
403 if (!SQL_SUCCEEDED(sr)) {
404 m_log.error("failed to allocate statement handle");
405 log_error(conn, SQL_HANDLE_DBC);
406 throw IOException("ODBC StorageService failed to allocate a statement handle.");
411 pair<SQLINTEGER,SQLINTEGER> ODBCStorageService::getVersion(SQLHDBC conn)
413 // Grab the version number from the database.
414 SQLHSTMT stmt = getHSTMT(conn);
416 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
417 if (!SQL_SUCCEEDED(sr)) {
418 m_log.error("failed to read version from database");
419 log_error(stmt, SQL_HANDLE_STMT);
420 throw IOException("ODBC StorageService failed to read version from database.");
425 SQLBindCol(stmt, 1, SQL_C_SLONG, &major, 0, nullptr);
426 SQLBindCol(stmt, 2, SQL_C_SLONG, &minor, 0, nullptr);
428 if ((sr = SQLFetch(stmt)) != SQL_NO_DATA)
429 return make_pair(major,minor);
431 m_log.error("no rows returned in version query");
432 throw IOException("ODBC StorageService failed to read version from database.");
435 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
438 xmltooling::NDC ndc("createRow");
442 timestampFromTime(expiration, timebuf);
444 // Get statement handle.
445 ODBCConn conn(getHDBC());
446 SQLHSTMT stmt = getHSTMT(conn);
448 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
450 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
451 if (!SQL_SUCCEEDED(sr)) {
452 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
453 log_error(stmt, SQL_HANDLE_STMT);
454 throw IOException("ODBC StorageService failed to insert record.");
456 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
458 SQLLEN b_ind = SQL_NTS;
459 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
460 if (!SQL_SUCCEEDED(sr)) {
461 m_log.error("SQLBindParam failed (context = %s)", context);
462 log_error(stmt, SQL_HANDLE_STMT);
463 throw IOException("ODBC StorageService failed to insert record.");
465 m_log.debug("SQLBindParam succeeded (context = %s)", context);
467 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
468 if (!SQL_SUCCEEDED(sr)) {
469 m_log.error("SQLBindParam failed (key = %s)", key);
470 log_error(stmt, SQL_HANDLE_STMT);
471 throw IOException("ODBC StorageService failed to insert record.");
473 m_log.debug("SQLBindParam succeeded (key = %s)", key);
475 if (strcmp(table, TEXT_TABLE)==0)
476 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
478 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
479 if (!SQL_SUCCEEDED(sr)) {
480 m_log.error("SQLBindParam failed (value = %s)", value);
481 log_error(stmt, SQL_HANDLE_STMT);
482 throw IOException("ODBC StorageService failed to insert record.");
484 m_log.debug("SQLBindParam succeeded (value = %s)", value);
487 pair<bool,bool> logres;
489 logres = make_pair(false,false);
491 sr = SQLExecute(stmt);
492 if (SQL_SUCCEEDED(sr)) {
493 m_log.debug("SQLExecute of insert succeeded");
496 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
497 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
499 return false; // supposedly integrity violation?
500 } while (attempts && logres.first);
502 throw IOException("ODBC StorageService failed to insert record.");
505 int ODBCStorageService::readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
508 xmltooling::NDC ndc("readRow");
511 // Get statement handle.
512 ODBCConn conn(getHDBC());
513 SQLHSTMT stmt = getHSTMT(conn);
515 // Prepare and exectute select statement.
517 timestampFromTime(time(nullptr), timebuf);
518 SQLString scontext(context);
520 string q("SELECT version");
525 q = q + ",CASE version WHEN " + lexical_cast<string>(version) + " THEN null ELSE value END";
527 q = q + " FROM " + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
528 if (m_log.isDebugEnabled())
529 m_log.debug("SQL: %s", q.c_str());
531 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
532 if (!SQL_SUCCEEDED(sr)) {
533 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
534 log_error(stmt, SQL_HANDLE_STMT);
535 throw IOException("ODBC StorageService search failed.");
539 SQL_TIMESTAMP_STRUCT expiration;
541 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
543 SQLBindCol(stmt, 2, SQL_C_TYPE_TIMESTAMP, &expiration, 0, nullptr);
545 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA)
549 *pexpiration = timeFromTimestamp(expiration);
552 return version; // nothing's changed, so just echo back the version
556 SQLCHAR buf[LONGDATA_BUFLEN];
557 while ((sr = SQLGetData(stmt, (pexpiration ? 3 : 2), SQL_C_CHAR, buf, sizeof(buf), &len)) != SQL_NO_DATA) {
558 if (!SQL_SUCCEEDED(sr)) {
559 m_log.error("error while reading text field from result set");
560 log_error(stmt, SQL_HANDLE_STMT);
561 throw IOException("ODBC StorageService search failed to read data from result set.");
563 pvalue->append((char*)buf);
570 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
573 xmltooling::NDC ndc("updateRow");
576 if (!value && !expiration)
577 throw IOException("ODBC StorageService given invalid update instructions.");
579 // Get statement handle. Disable auto-commit mode to wrap select + update.
580 ODBCConn conn(getHDBC());
581 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
582 if (!SQL_SUCCEEDED(sr))
583 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
584 conn.autoCommit = false;
585 SQLHSTMT stmt = getHSTMT(conn);
587 // First, fetch the current version for later, which also ensures the record still exists.
589 timestampFromTime(time(nullptr), timebuf);
590 SQLString scontext(context);
592 string q("SELECT version FROM ");
593 q = q + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
595 m_log.debug("SQL: %s", q.c_str());
597 sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
598 if (!SQL_SUCCEEDED(sr)) {
599 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
600 log_error(stmt, SQL_HANDLE_STMT);
601 throw IOException("ODBC StorageService search failed.");
605 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
606 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
611 if (version > 0 && version != ver) {
615 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
616 stmt = getHSTMT(conn);
618 // Prepare and exectute update statement.
619 q = string("UPDATE ") + table + " SET ";
622 q = q + "value=?, version=version+1";
625 timestampFromTime(expiration, timebuf);
628 q = q + "expires = " + timebuf;
631 q = q + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
633 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
634 if (!SQL_SUCCEEDED(sr)) {
635 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
636 log_error(stmt, SQL_HANDLE_STMT);
637 throw IOException("ODBC StorageService failed to update record.");
639 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
641 SQLLEN b_ind = SQL_NTS;
643 if (strcmp(table, TEXT_TABLE)==0)
644 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
646 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
647 if (!SQL_SUCCEEDED(sr)) {
648 m_log.error("SQLBindParam failed (context = %s)", context);
649 log_error(stmt, SQL_HANDLE_STMT);
650 throw IOException("ODBC StorageService failed to update record.");
652 m_log.debug("SQLBindParam succeeded (context = %s)", context);
656 pair<bool,bool> logres;
658 logres = make_pair(false,false);
660 sr = SQLExecute(stmt);
661 if (sr == SQL_NO_DATA)
662 return 0; // went missing?
663 else if (SQL_SUCCEEDED(sr)) {
664 m_log.debug("SQLExecute of update succeeded");
668 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
669 logres = log_error(stmt, SQL_HANDLE_STMT);
670 } while (attempts && logres.first);
672 throw IOException("ODBC StorageService failed to update record.");
675 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
678 xmltooling::NDC ndc("deleteRow");
681 // Get statement handle.
682 ODBCConn conn(getHDBC());
683 SQLHSTMT stmt = getHSTMT(conn);
685 // Prepare and execute delete statement.
686 SQLString scontext(context);
688 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
689 m_log.debug("SQL: %s", q.c_str());
691 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
692 if (sr == SQL_NO_DATA)
694 else if (!SQL_SUCCEEDED(sr)) {
695 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
696 log_error(stmt, SQL_HANDLE_STMT);
697 throw IOException("ODBC StorageService failed to delete record.");
704 void ODBCStorageService::cleanup()
707 xmltooling::NDC ndc("cleanup");
710 scoped_ptr<Mutex> mutex(Mutex::create());
714 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
717 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
723 catch (std::exception& ex) {
724 m_log.error("cleanup thread swallowed exception: %s", ex.what());
728 m_log.info("cleanup thread exiting...");
731 Thread::exit(nullptr);
734 void* ODBCStorageService::cleanup_fn(void* cache_p)
736 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
739 // First, let's block all signals
740 Thread::mask_all_signals();
743 // Now run the cleanup process.
748 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
751 xmltooling::NDC ndc("updateContext");
754 // Get statement handle.
755 ODBCConn conn(getHDBC());
756 SQLHSTMT stmt = getHSTMT(conn);
759 timestampFromTime(expiration, timebuf);
762 timestampFromTime(time(nullptr), nowbuf);
764 SQLString scontext(context);
765 string q = string("UPDATE ") + table + " SET expires = " + timebuf + " WHERE context='" + scontext.tostr() + "' AND expires > " + nowbuf;
767 m_log.debug("SQL: %s", q.c_str());
769 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
770 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
771 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
772 log_error(stmt, SQL_HANDLE_STMT);
773 throw IOException("ODBC StorageService failed to update context expiration.");
777 void ODBCStorageService::reap(const char *table, const char* context)
780 xmltooling::NDC ndc("reap");
783 // Get statement handle.
784 ODBCConn conn(getHDBC());
785 SQLHSTMT stmt = getHSTMT(conn);
787 // Prepare and execute delete statement.
789 timestampFromTime(time(nullptr), nowbuf);
792 SQLString scontext(context);
793 q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND expires <= " + nowbuf;
796 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
798 m_log.debug("SQL: %s", q.c_str());
800 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
801 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
802 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
803 log_error(stmt, SQL_HANDLE_STMT);
804 throw IOException("ODBC StorageService failed to purge expired records.");
808 void ODBCStorageService::deleteContext(const char *table, const char* context)
811 xmltooling::NDC ndc("deleteContext");
814 // Get statement handle.
815 ODBCConn conn(getHDBC());
816 SQLHSTMT stmt = getHSTMT(conn);
818 // Prepare and execute delete statement.
819 SQLString scontext(context);
820 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "'";
821 m_log.debug("SQL: %s", q.c_str());
823 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
824 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
825 m_log.error("error deleting context (t=%s, c=%s)", table, context);
826 log_error(stmt, SQL_HANDLE_STMT);
827 throw IOException("ODBC StorageService failed to delete context.");
831 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
833 // Register this SS type
834 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
838 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
840 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");