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);
521 logres.first = true; // force it to treat as a retryable error
526 } while (attempts && logres.first);
528 throw IOException("ODBC StorageService failed to insert record.");
531 int ODBCStorageService::readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version)
534 xmltooling::NDC ndc("readRow");
537 // Get statement handle.
538 ODBCConn conn(getHDBC());
539 SQLHSTMT stmt = getHSTMT(conn);
541 // Prepare and exectute select statement.
543 timestampFromTime(time(nullptr), timebuf);
544 SQLString scontext(context);
546 string q("SELECT version");
551 q = q + ",CASE version WHEN " + lexical_cast<string>(version) + " THEN null ELSE value END";
553 q = q + " FROM " + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
554 if (m_log.isDebugEnabled())
555 m_log.debug("SQL: %s", q.c_str());
557 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
558 if (!SQL_SUCCEEDED(sr)) {
559 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
560 log_error(stmt, SQL_HANDLE_STMT);
561 throw IOException("ODBC StorageService search failed.");
566 SQL_TIMESTAMP_STRUCT expiration;
569 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
571 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
573 SQLBindCol(stmt, 2, SQL_C_TYPE_TIMESTAMP, &expiration, 0, nullptr);
575 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
576 if (m_log.isDebugEnabled())
577 m_log.debug("search returned no data (t=%s, c=%s, k=%s)", table, context, key);
582 *pexpiration = timeFromTimestamp(expiration);
584 if (version == (m_wideVersion ? widever : ver)) {
585 if (m_log.isDebugEnabled())
586 m_log.debug("versioned search detected no change (t=%s, c=%s, k=%s)", table, context, key);
587 return version; // nothing's changed, so just echo back the version
592 SQLCHAR buf[LONGDATA_BUFLEN];
593 while ((sr = SQLGetData(stmt, (pexpiration ? 3 : 2), SQL_C_CHAR, buf, sizeof(buf), &len)) != SQL_NO_DATA) {
594 if (!SQL_SUCCEEDED(sr)) {
595 m_log.error("error while reading text field from result set");
596 log_error(stmt, SQL_HANDLE_STMT);
597 throw IOException("ODBC StorageService search failed to read data from result set.");
599 pvalue->append((char*)buf);
603 return (m_wideVersion ? widever : ver);
606 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
609 xmltooling::NDC ndc("updateRow");
612 if (!value && !expiration)
613 throw IOException("ODBC StorageService given invalid update instructions.");
615 // Get statement handle. Disable auto-commit mode to wrap select + update.
616 ODBCConn conn(getHDBC());
617 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
618 if (!SQL_SUCCEEDED(sr))
619 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
620 conn.autoCommit = false;
621 SQLHSTMT stmt = getHSTMT(conn);
623 // First, fetch the current version for later, which also ensures the record still exists.
625 timestampFromTime(time(nullptr), timebuf);
626 SQLString scontext(context);
628 string q("SELECT version FROM ");
629 q = q + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "' AND expires > " + timebuf;
631 m_log.debug("SQL: %s", q.c_str());
633 sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
634 if (!SQL_SUCCEEDED(sr)) {
635 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
636 log_error(stmt, SQL_HANDLE_STMT);
637 throw IOException("ODBC StorageService search failed.");
643 SQLBindCol(stmt, 1, SQL_C_SLONG, &widever, 0, nullptr);
645 SQLBindCol(stmt, 1, SQL_C_SSHORT, &ver, 0, nullptr);
646 if ((sr = SQLFetch(stmt)) == SQL_NO_DATA) {
651 if (version > 0 && version != (m_wideVersion ? widever : ver)) {
654 else if ((m_wideVersion && widever == INT_MAX) || (!m_wideVersion && ver == 32767)) {
655 m_log.error("record version overflow (t=%s, c=%s, k=%s)", table, context, key);
656 throw IOException("Version overflow, record in ODBC StorageService could not be updated.");
659 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
660 stmt = getHSTMT(conn);
662 // Prepare and exectute update statement.
663 q = string("UPDATE ") + table + " SET ";
666 q = q + "value=?, version=version+1";
669 timestampFromTime(expiration, timebuf);
672 q = q + "expires = " + timebuf;
675 q = q + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
677 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
678 if (!SQL_SUCCEEDED(sr)) {
679 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
680 log_error(stmt, SQL_HANDLE_STMT);
681 throw IOException("ODBC StorageService failed to update record.");
683 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
685 SQLLEN b_ind = SQL_NTS;
687 if (strcmp(table, TEXT_TABLE)==0)
688 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
690 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
691 if (!SQL_SUCCEEDED(sr)) {
692 m_log.error("SQLBindParam failed (value = %s)", value);
693 log_error(stmt, SQL_HANDLE_STMT);
694 throw IOException("ODBC StorageService failed to update record.");
696 m_log.debug("SQLBindParam succeeded (value = %s)", value);
700 pair<bool,bool> logres;
702 logres = make_pair(false,false);
704 sr = SQLExecute(stmt);
705 if (sr == SQL_NO_DATA)
706 return 0; // went missing?
707 else if (SQL_SUCCEEDED(sr)) {
708 m_log.debug("SQLExecute of update succeeded");
709 return (m_wideVersion ? widever : ver) + 1;
712 m_log.error("update of record failed (t=%s, c=%s, k=%s)", table, context, key);
713 logres = log_error(stmt, SQL_HANDLE_STMT);
714 } while (attempts && logres.first);
716 throw IOException("ODBC StorageService failed to update record.");
719 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
722 xmltooling::NDC ndc("deleteRow");
725 // Get statement handle.
726 ODBCConn conn(getHDBC());
727 SQLHSTMT stmt = getHSTMT(conn);
729 // Prepare and execute delete statement.
730 SQLString scontext(context);
732 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND id='" + skey.tostr() + "'";
733 m_log.debug("SQL: %s", q.c_str());
735 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
736 if (sr == SQL_NO_DATA)
738 else if (!SQL_SUCCEEDED(sr)) {
739 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
740 log_error(stmt, SQL_HANDLE_STMT);
741 throw IOException("ODBC StorageService failed to delete record.");
748 void ODBCStorageService::cleanup()
751 xmltooling::NDC ndc("cleanup");
754 scoped_ptr<Mutex> mutex(Mutex::create());
758 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
761 shutdown_wait->timedwait(mutex.get(), m_cleanupInterval);
767 catch (std::exception& ex) {
768 m_log.error("cleanup thread swallowed exception: %s", ex.what());
772 m_log.info("cleanup thread exiting...");
775 Thread::exit(nullptr);
778 void* ODBCStorageService::cleanup_fn(void* cache_p)
780 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
783 // First, let's block all signals
784 Thread::mask_all_signals();
787 // Now run the cleanup process.
792 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
795 xmltooling::NDC ndc("updateContext");
798 // Get statement handle.
799 ODBCConn conn(getHDBC());
800 SQLHSTMT stmt = getHSTMT(conn);
803 timestampFromTime(expiration, timebuf);
806 timestampFromTime(time(nullptr), nowbuf);
808 SQLString scontext(context);
809 string q = string("UPDATE ") + table + " SET expires = " + timebuf + " WHERE context='" + scontext.tostr() + "' AND expires > " + nowbuf;
811 m_log.debug("SQL: %s", q.c_str());
813 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
814 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
815 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
816 log_error(stmt, SQL_HANDLE_STMT);
817 throw IOException("ODBC StorageService failed to update context expiration.");
821 void ODBCStorageService::reap(const char *table, const char* context)
824 xmltooling::NDC ndc("reap");
827 // Get statement handle.
828 ODBCConn conn(getHDBC());
829 SQLHSTMT stmt = getHSTMT(conn);
831 // Prepare and execute delete statement.
833 timestampFromTime(time(nullptr), nowbuf);
836 SQLString scontext(context);
837 q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "' AND expires <= " + nowbuf;
840 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
842 m_log.debug("SQL: %s", q.c_str());
844 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
845 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
846 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
847 log_error(stmt, SQL_HANDLE_STMT);
848 throw IOException("ODBC StorageService failed to purge expired records.");
852 void ODBCStorageService::deleteContext(const char *table, const char* context)
855 xmltooling::NDC ndc("deleteContext");
858 // Get statement handle.
859 ODBCConn conn(getHDBC());
860 SQLHSTMT stmt = getHSTMT(conn);
862 // Prepare and execute delete statement.
863 SQLString scontext(context);
864 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext.tostr() + "'";
865 m_log.debug("SQL: %s", q.c_str());
867 SQLRETURN sr = SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
868 if ((sr != SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
869 m_log.error("error deleting context (t=%s, c=%s)", table, context);
870 log_error(stmt, SQL_HANDLE_STMT);
871 throw IOException("ODBC StorageService failed to delete context.");
875 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
877 // Register this SS type
878 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
882 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
884 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");