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 <xercesc/util/XMLUniDefs.hpp>
45 #include <xmltooling/logging.h>
46 #include <xmltooling/unicode.h>
47 #include <xmltooling/XMLToolingConfig.h>
48 #include <xmltooling/util/NDC.h>
49 #include <xmltooling/util/StorageService.h>
50 #include <xmltooling/util/Threads.h>
51 #include <xmltooling/util/XMLHelper.h>
56 using namespace xmltooling::logging;
57 using namespace xmltooling;
58 using namespace xercesc;
61 #define PLUGIN_VER_MAJOR 1
62 #define PLUGIN_VER_MINOR 0
64 #define LONGDATA_BUFLEN 16384
66 #define COLSIZE_CONTEXT 255
67 #define COLSIZE_ID 255
68 #define COLSIZE_STRING_VALUE 255
70 #define STRING_TABLE "strings"
71 #define TEXT_TABLE "texts"
74 CREATE TABLE version (
75 major int NOT nullptr,
79 CREATE TABLE strings (
80 context varchar(255) not null,
81 id varchar(255) not null,
82 expires datetime not null,
83 version smallint not null,
84 value varchar(255) not null,
85 PRIMARY KEY (context, id)
89 context varchar(255) not null,
90 id varchar(255) not null,
91 expires datetime not null,
92 version smallint not null,
94 PRIMARY KEY (context, id)
99 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
100 static const XMLCh isolationLevel[] = UNICODE_LITERAL_14(i,s,o,l,a,t,i,o,n,L,e,v,e,l);
101 static const XMLCh ConnectionString[] = UNICODE_LITERAL_16(C,o,n,n,e,c,t,i,o,n,S,t,r,i,n,g);
102 static const XMLCh RetryOnError[] = UNICODE_LITERAL_12(R,e,t,r,y,O,n,E,r,r,o,r);
103 static const XMLCh contextSize[] = UNICODE_LITERAL_11(c,o,n,t,e,x,t,S,i,z,e);
104 static const XMLCh keySize[] = UNICODE_LITERAL_7(k,e,y,S,i,z,e);
105 static const XMLCh stringSize[] = UNICODE_LITERAL_10(s,t,r,i,n,g,S,i,z,e);
107 // RAII for ODBC handles
109 ODBCConn(SQLHDBC conn) : handle(conn), autoCommit(true) {}
111 SQLRETURN sr = SQL_SUCCESS;
113 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
114 SQLDisconnect(handle);
115 SQLFreeHandle(SQL_HANDLE_DBC,handle);
116 if (!SQL_SUCCEEDED(sr))
117 throw IOException("Failed to commit connection and return to auto-commit mode.");
119 operator SQLHDBC() {return handle;}
124 class ODBCStorageService : public StorageService
127 ODBCStorageService(const DOMElement* e);
128 virtual ~ODBCStorageService();
130 const Capabilities& getCapabilities() const {
134 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
135 return createRow(STRING_TABLE, context, key, value, expiration);
137 int readString(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
138 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
140 int updateString(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
141 return updateRow(STRING_TABLE, context, key, value, expiration, version);
143 bool deleteString(const char* context, const char* key) {
144 return deleteRow(STRING_TABLE, context, key);
147 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
148 return createRow(TEXT_TABLE, context, key, value, expiration);
150 int readText(const char* context, const char* key, string* pvalue=nullptr, time_t* pexpiration=nullptr, int version=0) {
151 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
153 int updateText(const char* context, const char* key, const char* value=nullptr, time_t expiration=0, int version=0) {
154 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
156 bool deleteText(const char* context, const char* key) {
157 return deleteRow(TEXT_TABLE, context, key);
160 void reap(const char* context) {
161 reap(STRING_TABLE, context);
162 reap(TEXT_TABLE, context);
165 void updateContext(const char* context, time_t expiration) {
166 updateContext(STRING_TABLE, context, expiration);
167 updateContext(TEXT_TABLE, context, expiration);
170 void deleteContext(const char* context) {
171 deleteContext(STRING_TABLE, context);
172 deleteContext(TEXT_TABLE, context);
177 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
178 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
179 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
180 bool deleteRow(const char *table, const char* context, const char* key);
182 void reap(const char* table, const char* context);
183 void updateContext(const char* table, const char* context, time_t expiration);
184 void deleteContext(const char* table, const char* context);
187 SQLHSTMT getHSTMT(SQLHDBC);
188 pair<int,int> getVersion(SQLHDBC);
189 pair<bool,bool> log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=nullptr);
191 static void* cleanup_fn(void*);
196 int m_cleanupInterval;
197 CondWait* shutdown_wait;
198 Thread* cleanup_thread;
204 vector<SQLINTEGER> m_retries;
207 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
209 return new ODBCStorageService(e);
212 // convert SQL timestamp to time_t
213 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
217 t.tm_sec=expires.second;
218 t.tm_min=expires.minute;
219 t.tm_hour=expires.hour;
220 t.tm_mday=expires.day;
221 t.tm_mon=expires.month-1;
222 t.tm_year=expires.year-1900;
224 #if defined(HAVE_TIMEGM)
227 ret = mktime(&t) - timezone;
232 // conver time_t to SQL string
233 void timestampFromTime(time_t t, char* ret)
237 struct tm* ptime=gmtime_r(&t,&res);
239 struct tm* ptime=gmtime(&t);
241 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
244 // make a string safe for SQL command
245 // result to be free'd only if it isn't the input
246 static char *makeSafeSQL(const char *src)
252 // see if any conversion needed
253 for (s=(char*)src; *s; nc++,s++) if (*s=='\'') ns++;
254 if (ns==0) return ((char*)src);
256 char *safe = new char[(nc+2*ns+1)];
257 for (s=safe; *src; src++) {
258 if (*src=='\'') *s++ = '\'';
265 void freeSafeSQL(char *safe, const char *src)
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(900), shutdown_wait(nullptr), cleanup_thread(nullptr), shutdown(false), m_henv(SQL_NULL_HANDLE), m_isolation(SQL_TXN_SERIALIZABLE)
277 xmltooling::NDC ndc("ODBCStorageService");
280 const XMLCh* tag=e ? e->getAttributeNS(nullptr,cleanupInterval) : nullptr;
282 m_cleanupInterval = XMLString::parseInt(tag);
283 if (!m_cleanupInterval)
284 m_cleanupInterval = 900;
286 auto_ptr_char iso(e ? e->getAttributeNS(nullptr,isolationLevel) : nullptr);
287 if (iso.get() && *iso.get()) {
288 if (!strcmp(iso.get(),"SERIALIZABLE"))
289 m_isolation = SQL_TXN_SERIALIZABLE;
290 else if (!strcmp(iso.get(),"REPEATABLE_READ"))
291 m_isolation = SQL_TXN_REPEATABLE_READ;
292 else if (!strcmp(iso.get(),"READ_COMMITTED"))
293 m_isolation = SQL_TXN_READ_COMMITTED;
294 else if (!strcmp(iso.get(),"READ_UNCOMMITTED"))
295 m_isolation = SQL_TXN_READ_UNCOMMITTED;
297 throw XMLToolingException("Unknown transaction isolationLevel property.");
300 if (m_henv == SQL_NULL_HANDLE) {
301 // Enable connection pooling.
302 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
304 // Allocate the environment.
305 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
306 throw XMLToolingException("ODBC failed to initialize.");
309 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
311 m_log.info("ODBC initialized");
314 // Grab connection string from the configuration.
315 e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : nullptr;
316 if (!e || !e->hasChildNodes()) {
317 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
318 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
320 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
321 m_connstring=arg.get();
323 // Connect and check version.
324 ODBCConn conn(getHDBC());
325 pair<int,int> v=getVersion(conn);
327 // Make sure we've got the right version.
328 if (v.first != PLUGIN_VER_MAJOR) {
329 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
330 m_log.crit("unknown database version: %d.%d", v.first, v.second);
331 throw XMLToolingException("Unknown database version for ODBC StorageService.");
334 // Load any retry errors to check.
335 e = XMLHelper::getNextSiblingElement(e,RetryOnError);
337 if (e->hasChildNodes()) {
338 m_retries.push_back(XMLString::parseInt(e->getFirstChild()->getNodeValue()));
339 m_log.info("will retry operations when native ODBC error (%ld) is returned", m_retries.back());
341 e = XMLHelper::getNextSiblingElement(e,RetryOnError);
344 // Initialize the cleanup thread
345 shutdown_wait = CondWait::create();
346 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
349 ODBCStorageService::~ODBCStorageService()
352 shutdown_wait->signal();
353 cleanup_thread->join(nullptr);
354 delete shutdown_wait;
355 if (m_henv != SQL_NULL_HANDLE)
356 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
359 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
368 pair<bool,bool> res = make_pair(false,false);
370 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
371 if (SQL_SUCCEEDED(ret)) {
372 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
373 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
374 res.first = (*n == native);
375 if (checkfor && !strcmp(checkfor, (const char*)state))
378 } while(SQL_SUCCEEDED(ret));
382 SQLHDBC ODBCStorageService::getHDBC()
385 xmltooling::NDC ndc("getHDBC");
390 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
391 if (!SQL_SUCCEEDED(sr)) {
392 m_log.error("failed to allocate connection handle");
393 log_error(m_henv, SQL_HANDLE_ENV);
394 throw IOException("ODBC StorageService failed to allocate a connection handle.");
397 sr=SQLDriverConnect(handle,nullptr,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),nullptr,0,nullptr,SQL_DRIVER_NOPROMPT);
398 if (!SQL_SUCCEEDED(sr)) {
399 m_log.error("failed to connect to database");
400 log_error(handle, SQL_HANDLE_DBC);
401 throw IOException("ODBC StorageService failed to connect to database.");
404 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, 0);
405 if (!SQL_SUCCEEDED(sr))
406 throw IOException("ODBC StorageService failed to set transaction isolation level.");
411 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
414 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
415 if (!SQL_SUCCEEDED(sr)) {
416 m_log.error("failed to allocate statement handle");
417 log_error(conn, SQL_HANDLE_DBC);
418 throw IOException("ODBC StorageService failed to allocate a statement handle.");
423 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
425 // Grab the version number from the database.
426 SQLHSTMT stmt = getHSTMT(conn);
428 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
429 if (!SQL_SUCCEEDED(sr)) {
430 m_log.error("failed to read version from database");
431 log_error(stmt, SQL_HANDLE_STMT);
432 throw IOException("ODBC StorageService failed to read version from database.");
437 SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,nullptr);
438 SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,nullptr);
440 if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
441 return pair<int,int>(major,minor);
443 m_log.error("no rows returned in version query");
444 throw IOException("ODBC StorageService failed to read version from database.");
447 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
450 xmltooling::NDC ndc("createRow");
454 timestampFromTime(expiration, timebuf);
456 // Get statement handle.
457 ODBCConn conn(getHDBC());
458 SQLHSTMT stmt = getHSTMT(conn);
460 // Prepare and exectute insert statement.
461 //char *scontext = makeSafeSQL(context);
462 //char *skey = makeSafeSQL(key);
463 //char *svalue = makeSafeSQL(value);
464 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
466 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
467 if (!SQL_SUCCEEDED(sr)) {
468 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
469 log_error(stmt, SQL_HANDLE_STMT);
470 throw IOException("ODBC StorageService failed to insert record.");
472 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
474 SQLLEN b_ind = SQL_NTS;
475 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
476 if (!SQL_SUCCEEDED(sr)) {
477 m_log.error("SQLBindParam failed (context = %s)", context);
478 log_error(stmt, SQL_HANDLE_STMT);
479 throw IOException("ODBC StorageService failed to insert record.");
481 m_log.debug("SQLBindParam succeeded (context = %s)", context);
483 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
484 if (!SQL_SUCCEEDED(sr)) {
485 m_log.error("SQLBindParam failed (key = %s)", key);
486 log_error(stmt, SQL_HANDLE_STMT);
487 throw IOException("ODBC StorageService failed to insert record.");
489 m_log.debug("SQLBindParam succeeded (key = %s)", key);
491 if (strcmp(table, TEXT_TABLE)==0)
492 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
494 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
495 if (!SQL_SUCCEEDED(sr)) {
496 m_log.error("SQLBindParam failed (value = %s)", value);
497 log_error(stmt, SQL_HANDLE_STMT);
498 throw IOException("ODBC StorageService failed to insert record.");
500 m_log.debug("SQLBindParam succeeded (value = %s)", value);
502 //freeSafeSQL(scontext, context);
503 //freeSafeSQL(skey, key);
504 //freeSafeSQL(svalue, value);
505 //m_log.debug("SQL: %s", q.c_str());
508 pair<bool,bool> logres;
510 logres = make_pair(false,false);
513 if (SQL_SUCCEEDED(sr)) {
514 m_log.debug("SQLExecute of insert succeeded");
517 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
518 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
520 return false; // supposedly integrity violation?
521 } while (attempts && logres.first);
523 throw IOException("ODBC StorageService failed to insert record.");
526 int ODBCStorageService::readRow(
527 const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
531 xmltooling::NDC ndc("readRow");
534 // Get statement handle.
535 ODBCConn conn(getHDBC());
536 SQLHSTMT stmt = getHSTMT(conn);
538 // Prepare and exectute select statement.
540 timestampFromTime(time(nullptr), timebuf);
541 char *scontext = makeSafeSQL(context);
542 char *skey = makeSafeSQL(key);
544 q << "SELECT version";
548 q << ",CASE version WHEN " << version << " THEN null ELSE value END";
549 q << " FROM " << table << " WHERE context='" << scontext << "' AND id='" << skey << "' AND expires > " << timebuf;
550 freeSafeSQL(scontext, context);
551 freeSafeSQL(skey, key);
552 if (m_log.isDebugEnabled())
553 m_log.debug("SQL: %s", q.str().c_str());
555 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
556 if (!SQL_SUCCEEDED(sr)) {
557 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
558 log_error(stmt, SQL_HANDLE_STMT);
559 throw IOException("ODBC StorageService search failed.");
563 SQL_TIMESTAMP_STRUCT expiration;
565 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,nullptr);
567 SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,nullptr);
569 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
573 *pexpiration = timeFromTimestamp(expiration);
576 return version; // nothing's changed, so just echo back the version
580 SQLCHAR buf[LONGDATA_BUFLEN];
581 while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
582 if (!SQL_SUCCEEDED(sr)) {
583 m_log.error("error while reading text field from result set");
584 log_error(stmt, SQL_HANDLE_STMT);
585 throw IOException("ODBC StorageService search failed to read data from result set.");
587 pvalue->append((char*)buf);
594 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
597 xmltooling::NDC ndc("updateRow");
600 if (!value && !expiration)
601 throw IOException("ODBC StorageService given invalid update instructions.");
603 // Get statement handle. Disable auto-commit mode to wrap select + update.
604 ODBCConn conn(getHDBC());
605 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
606 if (!SQL_SUCCEEDED(sr))
607 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
608 conn.autoCommit = false;
609 SQLHSTMT stmt = getHSTMT(conn);
611 // First, fetch the current version for later, which also ensures the record still exists.
613 timestampFromTime(time(nullptr), timebuf);
614 char *scontext = makeSafeSQL(context);
615 char *skey = makeSafeSQL(key);
616 string q("SELECT version FROM ");
617 q = q + table + " WHERE context='" + scontext + "' AND id='" + skey + "' AND expires > " + timebuf;
619 m_log.debug("SQL: %s", q.c_str());
621 sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
622 if (!SQL_SUCCEEDED(sr)) {
623 freeSafeSQL(scontext, context);
624 freeSafeSQL(skey, key);
625 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
626 log_error(stmt, SQL_HANDLE_STMT);
627 throw IOException("ODBC StorageService search failed.");
631 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,nullptr);
632 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
633 freeSafeSQL(scontext, context);
634 freeSafeSQL(skey, key);
639 if (version > 0 && version != ver) {
640 freeSafeSQL(scontext, context);
641 freeSafeSQL(skey, key);
645 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
646 stmt = getHSTMT(conn);
648 // Prepare and exectute update statement.
649 q = string("UPDATE ") + table + " SET ";
652 q = q + "value=?, version=version+1";
655 timestampFromTime(expiration, timebuf);
658 q = q + "expires = " + timebuf;
661 q = q + " WHERE context='" + scontext + "' AND id='" + skey + "'";
662 freeSafeSQL(scontext, context);
663 freeSafeSQL(skey, key);
665 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
666 if (!SQL_SUCCEEDED(sr)) {
667 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
668 log_error(stmt, SQL_HANDLE_STMT);
669 throw IOException("ODBC StorageService failed to update record.");
671 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
673 SQLLEN b_ind = SQL_NTS;
675 if (strcmp(table, TEXT_TABLE)==0)
676 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
678 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
679 if (!SQL_SUCCEEDED(sr)) {
680 m_log.error("SQLBindParam failed (context = %s)", context);
681 log_error(stmt, SQL_HANDLE_STMT);
682 throw IOException("ODBC StorageService failed to update record.");
684 m_log.debug("SQLBindParam succeeded (context = %s)", context);
688 pair<bool,bool> logres;
690 logres = make_pair(false,false);
694 return 0; // went missing?
695 else if (SQL_SUCCEEDED(sr)) {
696 m_log.debug("SQLExecute of update succeeded");
700 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
701 logres = log_error(stmt, SQL_HANDLE_STMT);
702 } while (attempts && logres.first);
704 throw IOException("ODBC StorageService failed to update record.");
707 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
710 xmltooling::NDC ndc("deleteRow");
713 // Get statement handle.
714 ODBCConn conn(getHDBC());
715 SQLHSTMT stmt = getHSTMT(conn);
717 // Prepare and execute delete statement.
718 char *scontext = makeSafeSQL(context);
719 char *skey = makeSafeSQL(key);
720 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND id='" + skey + "'";
721 freeSafeSQL(scontext, context);
722 freeSafeSQL(skey, key);
723 m_log.debug("SQL: %s", q.c_str());
725 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
728 else if (!SQL_SUCCEEDED(sr)) {
729 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
730 log_error(stmt, SQL_HANDLE_STMT);
731 throw IOException("ODBC StorageService failed to delete record.");
738 void ODBCStorageService::cleanup()
741 xmltooling::NDC ndc("cleanup");
744 Mutex* mutex = Mutex::create();
748 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
751 shutdown_wait->timedwait(mutex, m_cleanupInterval);
757 catch (exception& ex) {
758 m_log.error("cleanup thread swallowed exception: %s", ex.what());
762 m_log.info("cleanup thread exiting...");
766 Thread::exit(nullptr);
769 void* ODBCStorageService::cleanup_fn(void* cache_p)
771 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
774 // First, let's block all signals
775 Thread::mask_all_signals();
778 // Now run the cleanup process.
783 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
786 xmltooling::NDC ndc("updateContext");
789 // Get statement handle.
790 ODBCConn conn(getHDBC());
791 SQLHSTMT stmt = getHSTMT(conn);
794 timestampFromTime(expiration, timebuf);
797 timestampFromTime(time(nullptr), nowbuf);
799 char *scontext = makeSafeSQL(context);
801 q = q + table + " SET expires = " + timebuf + " WHERE context='" + scontext + "' AND expires > " + nowbuf;
802 freeSafeSQL(scontext, context);
804 m_log.debug("SQL: %s", q.c_str());
806 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
807 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
808 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
809 log_error(stmt, SQL_HANDLE_STMT);
810 throw IOException("ODBC StorageService failed to update context expiration.");
814 void ODBCStorageService::reap(const char *table, const char* context)
817 xmltooling::NDC ndc("reap");
820 // Get statement handle.
821 ODBCConn conn(getHDBC());
822 SQLHSTMT stmt = getHSTMT(conn);
824 // Prepare and execute delete statement.
826 timestampFromTime(time(nullptr), nowbuf);
829 char *scontext = makeSafeSQL(context);
830 q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= " + nowbuf;
831 freeSafeSQL(scontext, context);
834 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
836 m_log.debug("SQL: %s", q.c_str());
838 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
839 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
840 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
841 log_error(stmt, SQL_HANDLE_STMT);
842 throw IOException("ODBC StorageService failed to purge expired records.");
846 void ODBCStorageService::deleteContext(const char *table, const char* context)
849 xmltooling::NDC ndc("deleteContext");
852 // Get statement handle.
853 ODBCConn conn(getHDBC());
854 SQLHSTMT stmt = getHSTMT(conn);
856 // Prepare and execute delete statement.
857 char *scontext = makeSafeSQL(context);
858 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
859 freeSafeSQL(scontext, context);
860 m_log.debug("SQL: %s", q.c_str());
862 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
863 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
864 m_log.error("error deleting context (t=%s, c=%s)", table, context);
865 log_error(stmt, SQL_HANDLE_STMT);
866 throw IOException("ODBC StorageService failed to delete context.");
870 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
872 // Register this SS type
873 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
877 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
879 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");