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()) {
336 int code = XMLString::parseInt(e->getTextContent());
337 m_retries.push_back(code);
338 m_log.info("will retry operations when native ODBC error (%d) is returned", code);
340 catch (XMLException&) {
341 m_log.error("skipping non-numeric ODBC retry code");
344 e = XMLHelper::getNextSiblingElement(e, RetryOnError);
347 // Initialize the cleanup thread
348 shutdown_wait.reset(CondWait::create());
349 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
352 ODBCStorageService::~ODBCStorageService()
355 shutdown_wait->signal();
356 cleanup_thread->join(nullptr);
357 if (m_henv != SQL_NULL_HANDLE)
358 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
361 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
370 pair<bool,bool> res = make_pair(false,false);
372 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
373 if (SQL_SUCCEEDED(ret)) {
374 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
375 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
376 res.first = (*n == native);
377 if (checkfor && !strcmp(checkfor, (const char*)state))
380 } while(SQL_SUCCEEDED(ret));
384 SQLHDBC ODBCStorageService::getHDBC()
387 xmltooling::NDC ndc("getHDBC");
391 SQLHDBC handle = SQL_NULL_HDBC;
392 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
393 if (!SQL_SUCCEEDED(sr) || handle == SQL_NULL_HDBC) {
394 m_log.error("failed to allocate connection handle");
395 log_error(m_henv, SQL_HANDLE_ENV);
396 throw IOException("ODBC StorageService failed to allocate a connection handle.");
399 sr = SQLDriverConnect(handle,nullptr,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),nullptr,0,nullptr,SQL_DRIVER_NOPROMPT);
400 if (!SQL_SUCCEEDED(sr)) {
401 m_log.error("failed to connect to database");
402 log_error(handle, SQL_HANDLE_DBC);
403 SQLFreeHandle(SQL_HANDLE_DBC, handle);
404 throw IOException("ODBC StorageService failed to connect to database.");
407 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, 0);
408 if (!SQL_SUCCEEDED(sr)) {
409 SQLDisconnect(handle);
410 SQLFreeHandle(SQL_HANDLE_DBC, handle);
411 throw IOException("ODBC StorageService failed to set transaction isolation level.");
417 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
419 SQLHSTMT hstmt = SQL_NULL_HSTMT;
420 SQLRETURN sr = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
421 if (!SQL_SUCCEEDED(sr) || hstmt == SQL_NULL_HSTMT) {
422 m_log.error("failed to allocate statement handle");
423 log_error(conn, SQL_HANDLE_DBC);
424 throw IOException("ODBC StorageService failed to allocate a statement handle.");
429 pair<SQLINTEGER,SQLINTEGER> ODBCStorageService::getVersion(SQLHDBC conn)
431 // Grab the version number from the database.
432 SQLHSTMT stmt = getHSTMT(conn);
434 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
435 if (!SQL_SUCCEEDED(sr)) {
436 m_log.error("failed to read version from database");
437 log_error(stmt, SQL_HANDLE_STMT);
438 throw IOException("ODBC StorageService failed to read version from database.");
443 SQLBindCol(stmt, 1, SQL_C_SLONG, &major, 0, nullptr);
444 SQLBindCol(stmt, 2, SQL_C_SLONG, &minor, 0, nullptr);
446 if ((sr = SQLFetch(stmt)) != SQL_NO_DATA)
447 return make_pair(major,minor);
449 m_log.error("no rows returned in version query");
450 throw IOException("ODBC StorageService failed to read version from database.");
453 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
456 xmltooling::NDC ndc("createRow");
460 timestampFromTime(expiration, timebuf);
462 // Get statement handle.
463 ODBCConn conn(getHDBC());
464 SQLHSTMT stmt = getHSTMT(conn);
466 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
468 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
469 if (!SQL_SUCCEEDED(sr)) {
470 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
471 log_error(stmt, SQL_HANDLE_STMT);
472 throw IOException("ODBC StorageService failed to insert record.");
474 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
476 SQLLEN b_ind = SQL_NTS;
477 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
478 if (!SQL_SUCCEEDED(sr)) {
479 m_log.error("SQLBindParam failed (context = %s)", context);
480 log_error(stmt, SQL_HANDLE_STMT);
481 throw IOException("ODBC StorageService failed to insert record.");
483 m_log.debug("SQLBindParam succeeded (context = %s)", context);
485 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
486 if (!SQL_SUCCEEDED(sr)) {
487 m_log.error("SQLBindParam failed (key = %s)", key);
488 log_error(stmt, SQL_HANDLE_STMT);
489 throw IOException("ODBC StorageService failed to insert record.");
491 m_log.debug("SQLBindParam succeeded (key = %s)", key);
493 if (strcmp(table, TEXT_TABLE)==0)
494 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
496 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
497 if (!SQL_SUCCEEDED(sr)) {
498 m_log.error("SQLBindParam failed (value = %s)", value);
499 log_error(stmt, SQL_HANDLE_STMT);
500 throw IOException("ODBC StorageService failed to insert record.");
502 m_log.debug("SQLBindParam succeeded (value = %s)", value);
505 pair<bool,bool> logres;
507 logres = make_pair(false,false);
509 sr = SQLExecute(stmt);
510 if (SQL_SUCCEEDED(sr)) {
511 m_log.debug("SQLExecute of insert succeeded");
514 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
515 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
517 return false; // supposedly integrity violation?
518 } while (attempts && logres.first);
520 throw IOException("ODBC StorageService failed to insert record.");
523 int ODBCStorageService::readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
526 xmltooling::NDC ndc("readRow");
529 // Get statement handle.
530 ODBCConn conn(getHDBC());
531 SQLHSTMT stmt = getHSTMT(conn);
533 // Prepare and exectute select statement.
535 timestampFromTime(time(nullptr), timebuf);
536 SQLString scontext(context);
538 string q("SELECT version");
543 q = q + ",CASE version WHEN " + lexical_cast<string>(version) + " THEN null ELSE value END";
545 q = q + " FROM " + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
546 if (m_log.isDebugEnabled())
547 m_log.debug("SQL: %s", q.c_str());
549 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
550 if (!SQL_SUCCEEDED(sr)) {
551 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
552 log_error(stmt, SQL_HANDLE_STMT);
553 throw IOException("ODBC StorageService search failed.");
558 SQL_TIMESTAMP_STRUCT expiration;
561 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
563 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
565 SQLBindCol(stmt, 2, SQL_C_TYPE_TIMESTAMP, &expiration, 0, nullptr);
567 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
568 if (m_log.isDebugEnabled())
569 m_log.debug("search returned no data (t=%s, c=%s, k=%s)", table, context, key);
574 *pexpiration = timeFromTimestamp(expiration);
576 if (version == (m_wideVersion ? widever : ver)) {
577 if (m_log.isDebugEnabled())
578 m_log.debug("versioned search detected no change (t=%s, c=%s, k=%s)", table, context, key);
579 return version; // nothing's changed, so just echo back the version
584 SQLCHAR buf[LONGDATA_BUFLEN];
585 while ((sr = SQLGetData(stmt, (pexpiration ? 3 : 2), SQL_C_CHAR, buf, sizeof(buf), &len)) != SQL_NO_DATA) {
586 if (!SQL_SUCCEEDED(sr)) {
587 m_log.error("error while reading text field from result set");
588 log_error(stmt, SQL_HANDLE_STMT);
589 throw IOException("ODBC StorageService search failed to read data from result set.");
591 pvalue->append((char*)buf);
595 return (m_wideVersion ? widever : ver);
598 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
601 xmltooling::NDC ndc("updateRow");
604 if (!value && !expiration)
605 throw IOException("ODBC StorageService given invalid update instructions.");
607 // Get statement handle. Disable auto-commit mode to wrap select + update.
608 ODBCConn conn(getHDBC());
609 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
610 if (!SQL_SUCCEEDED(sr))
611 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
612 conn.autoCommit = false;
613 SQLHSTMT stmt = getHSTMT(conn);
615 // First, fetch the current version for later, which also ensures the record still exists.
617 timestampFromTime(time(nullptr), timebuf);
618 SQLString scontext(context);
620 string q("SELECT version FROM ");
621 q = q + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
623 m_log.debug("SQL: %s", q.c_str());
625 sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
626 if (!SQL_SUCCEEDED(sr)) {
627 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
628 log_error(stmt, SQL_HANDLE_STMT);
629 throw IOException("ODBC StorageService search failed.");
635 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
637 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
638 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
643 if (version > 0 && version != (m_wideVersion ? widever : ver)) {
646 else if ((m_wideVersion && widever == INT_MAX) || (!m_wideVersion && ver == 32767)) {
647 m_log.error("record version overflow (t=%s, c=%s, k=%s)", table, context, key);
648 throw IOException("Version overflow, record in ODBC StorageService could not be updated.");
651 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
652 stmt = getHSTMT(conn);
654 // Prepare and exectute update statement.
655 q = string("UPDATE ") + table + " SET ";
658 q = q + "value=?, version=version+1";
661 timestampFromTime(expiration, timebuf);
664 q = q + "expires = " + timebuf;
667 q = q + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
669 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
670 if (!SQL_SUCCEEDED(sr)) {
671 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
672 log_error(stmt, SQL_HANDLE_STMT);
673 throw IOException("ODBC StorageService failed to update record.");
675 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
677 SQLLEN b_ind = SQL_NTS;
679 if (strcmp(table, TEXT_TABLE)==0)
680 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
682 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
683 if (!SQL_SUCCEEDED(sr)) {
684 m_log.error("SQLBindParam failed (value = %s)", value);
685 log_error(stmt, SQL_HANDLE_STMT);
686 throw IOException("ODBC StorageService failed to update record.");
688 m_log.debug("SQLBindParam succeeded (value = %s)", value);
692 pair<bool,bool> logres;
694 logres = make_pair(false,false);
696 sr = SQLExecute(stmt);
697 if (sr == SQL_NO_DATA)
698 return 0; // went missing?
699 else if (SQL_SUCCEEDED(sr)) {
700 m_log.debug("SQLExecute of update succeeded");
701 return (m_wideVersion ? widever : ver) + 1;
704 m_log.error("update of record failed (t=%s, c=%s, k=%s)", table, context, key);
705 logres = log_error(stmt, SQL_HANDLE_STMT);
706 } while (attempts && logres.first);
708 throw IOException("ODBC StorageService failed to update record.");
711 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
714 xmltooling::NDC ndc("deleteRow");
717 // Get statement handle.
718 ODBCConn conn(getHDBC());
719 SQLHSTMT stmt = getHSTMT(conn);
721 // Prepare and execute delete statement.
722 SQLString scontext(context);
724 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
725 m_log.debug("SQL: %s", q.c_str());
727 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
728 if (sr == SQL_NO_DATA)
730 else if (!SQL_SUCCEEDED(sr)) {
731 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
732 log_error(stmt, SQL_HANDLE_STMT);
733 throw IOException("ODBC StorageService failed to delete record.");
740 void ODBCStorageService::cleanup()
743 xmltooling::NDC ndc("cleanup");
746 scoped_ptr<Mutex> mutex(Mutex::create());
750 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
753 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
759 catch (std::exception& ex) {
760 m_log.error("cleanup thread swallowed exception: %s", ex.what());
764 m_log.info("cleanup thread exiting...");
767 Thread::exit(nullptr);
770 void* ODBCStorageService::cleanup_fn(void* cache_p)
772 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
775 // First, let's block all signals
776 Thread::mask_all_signals();
779 // Now run the cleanup process.
784 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
787 xmltooling::NDC ndc("updateContext");
790 // Get statement handle.
791 ODBCConn conn(getHDBC());
792 SQLHSTMT stmt = getHSTMT(conn);
795 timestampFromTime(expiration, timebuf);
798 timestampFromTime(time(nullptr), nowbuf);
800 SQLString scontext(context);
801 string q = string("UPDATE ") + table + " SET expires = " + timebuf + " WHERE context='" + scontext.tostr() + "' AND expires > " + nowbuf;
803 m_log.debug("SQL: %s", q.c_str());
805 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
806 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
807 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
808 log_error(stmt, SQL_HANDLE_STMT);
809 throw IOException("ODBC StorageService failed to update context expiration.");
813 void ODBCStorageService::reap(const char *table, const char* context)
816 xmltooling::NDC ndc("reap");
819 // Get statement handle.
820 ODBCConn conn(getHDBC());
821 SQLHSTMT stmt = getHSTMT(conn);
823 // Prepare and execute delete statement.
825 timestampFromTime(time(nullptr), nowbuf);
828 SQLString scontext(context);
829 q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND expires <= " + nowbuf;
832 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
834 m_log.debug("SQL: %s", q.c_str());
836 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
837 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
838 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
839 log_error(stmt, SQL_HANDLE_STMT);
840 throw IOException("ODBC StorageService failed to purge expired records.");
844 void ODBCStorageService::deleteContext(const char *table, const char* context)
847 xmltooling::NDC ndc("deleteContext");
850 // Get statement handle.
851 ODBCConn conn(getHDBC());
852 SQLHSTMT stmt = getHSTMT(conn);
854 // Prepare and execute delete statement.
855 SQLString scontext(context);
856 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "'";
857 m_log.debug("SQL: %s", q.c_str());
859 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
860 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
861 m_log.error("error deleting context (t=%s, c=%s)", table, context);
862 log_error(stmt, SQL_HANDLE_STMT);
863 throw IOException("ODBC StorageService failed to delete context.");
867 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
869 // Register this SS type
870 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
874 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
876 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");