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 // Supposedly integrity violation.
518 // Try and delete any expired record still hanging around until the final attempt.
520 reap(table, context);
525 } while (attempts && logres.first);
527 throw IOException("ODBC StorageService failed to insert record.");
530 int ODBCStorageService::readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
533 xmltooling::NDC ndc("readRow");
536 // Get statement handle.
537 ODBCConn conn(getHDBC());
538 SQLHSTMT stmt = getHSTMT(conn);
540 // Prepare and exectute select statement.
542 timestampFromTime(time(nullptr), timebuf);
543 SQLString scontext(context);
545 string q("SELECT version");
550 q = q + ",CASE version WHEN " + lexical_cast<string>(version) + " THEN null ELSE value END";
552 q = q + " FROM " + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
553 if (m_log.isDebugEnabled())
554 m_log.debug("SQL: %s", q.c_str());
556 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
557 if (!SQL_SUCCEEDED(sr)) {
558 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
559 log_error(stmt, SQL_HANDLE_STMT);
560 throw IOException("ODBC StorageService search failed.");
565 SQL_TIMESTAMP_STRUCT expiration;
568 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
570 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
572 SQLBindCol(stmt, 2, SQL_C_TYPE_TIMESTAMP, &expiration, 0, nullptr);
574 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
575 if (m_log.isDebugEnabled())
576 m_log.debug("search returned no data (t=%s, c=%s, k=%s)", table, context, key);
581 *pexpiration = timeFromTimestamp(expiration);
583 if (version == (m_wideVersion ? widever : ver)) {
584 if (m_log.isDebugEnabled())
585 m_log.debug("versioned search detected no change (t=%s, c=%s, k=%s)", table, context, key);
586 return version; // nothing's changed, so just echo back the version
591 SQLCHAR buf[LONGDATA_BUFLEN];
592 while ((sr = SQLGetData(stmt, (pexpiration ? 3 : 2), SQL_C_CHAR, buf, sizeof(buf), &len)) != SQL_NO_DATA) {
593 if (!SQL_SUCCEEDED(sr)) {
594 m_log.error("error while reading text field from result set");
595 log_error(stmt, SQL_HANDLE_STMT);
596 throw IOException("ODBC StorageService search failed to read data from result set.");
598 pvalue->append((char*)buf);
602 return (m_wideVersion ? widever : ver);
605 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
608 xmltooling::NDC ndc("updateRow");
611 if (!value && !expiration)
612 throw IOException("ODBC StorageService given invalid update instructions.");
614 // Get statement handle. Disable auto-commit mode to wrap select + update.
615 ODBCConn conn(getHDBC());
616 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
617 if (!SQL_SUCCEEDED(sr))
618 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
619 conn.autoCommit = false;
620 SQLHSTMT stmt = getHSTMT(conn);
622 // First, fetch the current version for later, which also ensures the record still exists.
624 timestampFromTime(time(nullptr), timebuf);
625 SQLString scontext(context);
627 string q("SELECT version FROM ");
628 q = q + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
630 m_log.debug("SQL: %s", q.c_str());
632 sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
633 if (!SQL_SUCCEEDED(sr)) {
634 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
635 log_error(stmt, SQL_HANDLE_STMT);
636 throw IOException("ODBC StorageService search failed.");
642 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
644 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
645 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
650 if (version > 0 && version != (m_wideVersion ? widever : ver)) {
653 else if ((m_wideVersion && widever == INT_MAX) || (!m_wideVersion && ver == 32767)) {
654 m_log.error("record version overflow (t=%s, c=%s, k=%s)", table, context, key);
655 throw IOException("Version overflow, record in ODBC StorageService could not be updated.");
658 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
659 stmt = getHSTMT(conn);
661 // Prepare and exectute update statement.
662 q = string("UPDATE ") + table + " SET ";
665 q = q + "value=?, version=version+1";
668 timestampFromTime(expiration, timebuf);
671 q = q + "expires = " + timebuf;
674 q = q + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
676 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
677 if (!SQL_SUCCEEDED(sr)) {
678 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
679 log_error(stmt, SQL_HANDLE_STMT);
680 throw IOException("ODBC StorageService failed to update record.");
682 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
684 SQLLEN b_ind = SQL_NTS;
686 if (strcmp(table, TEXT_TABLE)==0)
687 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
689 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
690 if (!SQL_SUCCEEDED(sr)) {
691 m_log.error("SQLBindParam failed (value = %s)", value);
692 log_error(stmt, SQL_HANDLE_STMT);
693 throw IOException("ODBC StorageService failed to update record.");
695 m_log.debug("SQLBindParam succeeded (value = %s)", value);
699 pair<bool,bool> logres;
701 logres = make_pair(false,false);
703 sr = SQLExecute(stmt);
704 if (sr == SQL_NO_DATA)
705 return 0; // went missing?
706 else if (SQL_SUCCEEDED(sr)) {
707 m_log.debug("SQLExecute of update succeeded");
708 return (m_wideVersion ? widever : ver) + 1;
711 m_log.error("update of record failed (t=%s, c=%s, k=%s)", table, context, key);
712 logres = log_error(stmt, SQL_HANDLE_STMT);
713 } while (attempts && logres.first);
715 throw IOException("ODBC StorageService failed to update record.");
718 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
721 xmltooling::NDC ndc("deleteRow");
724 // Get statement handle.
725 ODBCConn conn(getHDBC());
726 SQLHSTMT stmt = getHSTMT(conn);
728 // Prepare and execute delete statement.
729 SQLString scontext(context);
731 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
732 m_log.debug("SQL: %s", q.c_str());
734 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
735 if (sr == SQL_NO_DATA)
737 else if (!SQL_SUCCEEDED(sr)) {
738 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
739 log_error(stmt, SQL_HANDLE_STMT);
740 throw IOException("ODBC StorageService failed to delete record.");
747 void ODBCStorageService::cleanup()
750 xmltooling::NDC ndc("cleanup");
753 scoped_ptr<Mutex> mutex(Mutex::create());
757 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
760 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
766 catch (std::exception& ex) {
767 m_log.error("cleanup thread swallowed exception: %s", ex.what());
771 m_log.info("cleanup thread exiting...");
774 Thread::exit(nullptr);
777 void* ODBCStorageService::cleanup_fn(void* cache_p)
779 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
782 // First, let's block all signals
783 Thread::mask_all_signals();
786 // Now run the cleanup process.
791 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
794 xmltooling::NDC ndc("updateContext");
797 // Get statement handle.
798 ODBCConn conn(getHDBC());
799 SQLHSTMT stmt = getHSTMT(conn);
802 timestampFromTime(expiration, timebuf);
805 timestampFromTime(time(nullptr), nowbuf);
807 SQLString scontext(context);
808 string q = string("UPDATE ") + table + " SET expires = " + timebuf + " WHERE context='" + scontext.tostr() + "' AND expires > " + nowbuf;
810 m_log.debug("SQL: %s", q.c_str());
812 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
813 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
814 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
815 log_error(stmt, SQL_HANDLE_STMT);
816 throw IOException("ODBC StorageService failed to update context expiration.");
820 void ODBCStorageService::reap(const char *table, const char* context)
823 xmltooling::NDC ndc("reap");
826 // Get statement handle.
827 ODBCConn conn(getHDBC());
828 SQLHSTMT stmt = getHSTMT(conn);
830 // Prepare and execute delete statement.
832 timestampFromTime(time(nullptr), nowbuf);
835 SQLString scontext(context);
836 q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND expires <= " + nowbuf;
839 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
841 m_log.debug("SQL: %s", q.c_str());
843 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
844 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
845 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
846 log_error(stmt, SQL_HANDLE_STMT);
847 throw IOException("ODBC StorageService failed to purge expired records.");
851 void ODBCStorageService::deleteContext(const char *table, const char* context)
854 xmltooling::NDC ndc("deleteContext");
857 // Get statement handle.
858 ODBCConn conn(getHDBC());
859 SQLHSTMT stmt = getHSTMT(conn);
861 // Prepare and execute delete statement.
862 SQLString scontext(context);
863 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "'";
864 m_log.debug("SQL: %s", q.c_str());
866 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
867 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
868 m_log.error("error deleting context (t=%s, c=%s)", table, context);
869 log_error(stmt, SQL_HANDLE_STMT);
870 throw IOException("ODBC StorageService failed to delete context.");
874 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
876 // Register this SS type
877 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
881 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
883 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");