2 * Copyright 2001-2009 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * Storage Service using ODBC.
23 #if defined (_MSC_VER) || defined(__BORLANDC__)
24 # include "config_win32.h"
30 # define _CRT_NONSTDC_NO_DEPRECATE 1
31 # define _CRT_SECURE_NO_DEPRECATE 1
35 # define ODBCSTORE_EXPORTS __declspec(dllexport)
37 # define ODBCSTORE_EXPORTS
40 #include <xercesc/util/XMLUniDefs.hpp>
41 #include <xmltooling/logging.h>
42 #include <xmltooling/unicode.h>
43 #include <xmltooling/XMLToolingConfig.h>
44 #include <xmltooling/util/NDC.h>
45 #include <xmltooling/util/StorageService.h>
46 #include <xmltooling/util/Threads.h>
47 #include <xmltooling/util/XMLHelper.h>
52 using namespace xmltooling::logging;
53 using namespace xmltooling;
54 using namespace xercesc;
57 #define PLUGIN_VER_MAJOR 1
58 #define PLUGIN_VER_MINOR 0
60 #define LONGDATA_BUFLEN 16384
62 #define COLSIZE_CONTEXT 255
63 #define COLSIZE_ID 255
64 #define COLSIZE_STRING_VALUE 255
66 #define STRING_TABLE "strings"
67 #define TEXT_TABLE "texts"
70 CREATE TABLE version (
75 CREATE TABLE strings (
76 context varchar(255) not null,
77 id varchar(255) not null,
78 expires datetime not null,
79 version smallint not null,
80 value varchar(255) not null,
81 PRIMARY KEY (context, id)
85 context varchar(255) not null,
86 id varchar(255) not null,
87 expires datetime not null,
88 version smallint not null,
90 PRIMARY KEY (context, id)
95 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
96 static const XMLCh isolationLevel[] = UNICODE_LITERAL_14(i,s,o,l,a,t,i,o,n,L,e,v,e,l);
97 static const XMLCh ConnectionString[] = UNICODE_LITERAL_16(C,o,n,n,e,c,t,i,o,n,S,t,r,i,n,g);
98 static const XMLCh RetryOnError[] = UNICODE_LITERAL_12(R,e,t,r,y,O,n,E,r,r,o,r);
100 // RAII for ODBC handles
102 ODBCConn(SQLHDBC conn) : handle(conn), autoCommit(true) {}
104 SQLRETURN sr = SQL_SUCCESS;
106 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, NULL);
107 SQLDisconnect(handle);
108 SQLFreeHandle(SQL_HANDLE_DBC,handle);
109 if (!SQL_SUCCEEDED(sr))
110 throw IOException("Failed to commit connection and return to auto-commit mode.");
112 operator SQLHDBC() {return handle;}
117 class ODBCStorageService : public StorageService
120 ODBCStorageService(const DOMElement* e);
121 virtual ~ODBCStorageService();
123 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
124 return createRow(STRING_TABLE, context, key, value, expiration);
126 int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
127 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
129 int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
130 return updateRow(STRING_TABLE, context, key, value, expiration, version);
132 bool deleteString(const char* context, const char* key) {
133 return deleteRow(STRING_TABLE, context, key);
136 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
137 return createRow(TEXT_TABLE, context, key, value, expiration);
139 int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
140 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
142 int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
143 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
145 bool deleteText(const char* context, const char* key) {
146 return deleteRow(TEXT_TABLE, context, key);
149 void reap(const char* context) {
150 reap(STRING_TABLE, context);
151 reap(TEXT_TABLE, context);
154 void updateContext(const char* context, time_t expiration) {
155 updateContext(STRING_TABLE, context, expiration);
156 updateContext(TEXT_TABLE, context, expiration);
159 void deleteContext(const char* context) {
160 deleteContext(STRING_TABLE, context);
161 deleteContext(TEXT_TABLE, context);
166 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
167 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
168 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
169 bool deleteRow(const char *table, const char* context, const char* key);
171 void reap(const char* table, const char* context);
172 void updateContext(const char* table, const char* context, time_t expiration);
173 void deleteContext(const char* table, const char* context);
176 SQLHSTMT getHSTMT(SQLHDBC);
177 pair<int,int> getVersion(SQLHDBC);
178 pair<bool,bool> log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=NULL);
180 static void* cleanup_fn(void*);
184 int m_cleanupInterval;
185 CondWait* shutdown_wait;
186 Thread* cleanup_thread;
192 vector<SQLINTEGER> m_retries;
195 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
197 return new ODBCStorageService(e);
200 // convert SQL timestamp to time_t
201 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
205 t.tm_sec=expires.second;
206 t.tm_min=expires.minute;
207 t.tm_hour=expires.hour;
208 t.tm_mday=expires.day;
209 t.tm_mon=expires.month-1;
210 t.tm_year=expires.year-1900;
212 #if defined(HAVE_TIMEGM)
215 ret = mktime(&t) - timezone;
220 // conver time_t to SQL string
221 void timestampFromTime(time_t t, char* ret)
225 struct tm* ptime=gmtime_r(&t,&res);
227 struct tm* ptime=gmtime(&t);
229 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
232 // make a string safe for SQL command
233 // result to be free'd only if it isn't the input
234 static char *makeSafeSQL(const char *src)
240 // see if any conversion needed
241 for (s=(char*)src; *s; nc++,s++) if (*s=='\'') ns++;
242 if (ns==0) return ((char*)src);
244 char *safe = new char[(nc+2*ns+1)];
245 for (s=safe; *src; src++) {
246 if (*src=='\'') *s++ = '\'';
253 void freeSafeSQL(char *safe, const char *src)
260 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
261 m_cleanupInterval(900), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_henv(SQL_NULL_HANDLE), m_isolation(SQL_TXN_SERIALIZABLE)
264 xmltooling::NDC ndc("ODBCStorageService");
267 const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
269 m_cleanupInterval = XMLString::parseInt(tag);
270 if (!m_cleanupInterval)
271 m_cleanupInterval = 900;
273 auto_ptr_char iso(e ? e->getAttributeNS(NULL,isolationLevel) : NULL);
274 if (iso.get() && *iso.get()) {
275 if (!strcmp(iso.get(),"SERIALIZABLE"))
276 m_isolation = SQL_TXN_SERIALIZABLE;
277 else if (!strcmp(iso.get(),"REPEATABLE_READ"))
278 m_isolation = SQL_TXN_REPEATABLE_READ;
279 else if (!strcmp(iso.get(),"READ_COMMITTED"))
280 m_isolation = SQL_TXN_READ_COMMITTED;
281 else if (!strcmp(iso.get(),"READ_UNCOMMITTED"))
282 m_isolation = SQL_TXN_READ_UNCOMMITTED;
284 throw XMLToolingException("Unknown transaction isolationLevel property.");
287 if (m_henv == SQL_NULL_HANDLE) {
288 // Enable connection pooling.
289 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
291 // Allocate the environment.
292 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
293 throw XMLToolingException("ODBC failed to initialize.");
296 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
298 m_log.info("ODBC initialized");
301 // Grab connection string from the configuration.
302 e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : NULL;
303 if (!e || !e->hasChildNodes()) {
304 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
305 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
307 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
308 m_connstring=arg.get();
310 // Connect and check version.
311 ODBCConn conn(getHDBC());
312 pair<int,int> v=getVersion(conn);
314 // Make sure we've got the right version.
315 if (v.first != PLUGIN_VER_MAJOR) {
316 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
317 m_log.crit("unknown database version: %d.%d", v.first, v.second);
318 throw XMLToolingException("Unknown database version for ODBC StorageService.");
321 // Load any retry errors to check.
322 e = XMLHelper::getNextSiblingElement(e,RetryOnError);
324 if (e->hasChildNodes()) {
325 m_retries.push_back(XMLString::parseInt(e->getFirstChild()->getNodeValue()));
326 m_log.info("will retry operations when native ODBC error (%ld) is returned", m_retries.back());
328 e = XMLHelper::getNextSiblingElement(e,RetryOnError);
331 // Initialize the cleanup thread
332 shutdown_wait = CondWait::create();
333 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
336 ODBCStorageService::~ODBCStorageService()
339 shutdown_wait->signal();
340 cleanup_thread->join(NULL);
341 delete shutdown_wait;
342 if (m_henv != SQL_NULL_HANDLE)
343 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
346 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
355 pair<bool,bool> res = make_pair(false,false);
357 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
358 if (SQL_SUCCEEDED(ret)) {
359 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
360 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
361 res.first = (*n == native);
362 if (checkfor && !strcmp(checkfor, (const char*)state))
365 } while(SQL_SUCCEEDED(ret));
369 SQLHDBC ODBCStorageService::getHDBC()
372 xmltooling::NDC ndc("getHDBC");
377 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
378 if (!SQL_SUCCEEDED(sr)) {
379 m_log.error("failed to allocate connection handle");
380 log_error(m_henv, SQL_HANDLE_ENV);
381 throw IOException("ODBC StorageService failed to allocate a connection handle.");
384 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
385 if (!SQL_SUCCEEDED(sr)) {
386 m_log.error("failed to connect to database");
387 log_error(handle, SQL_HANDLE_DBC);
388 throw IOException("ODBC StorageService failed to connect to database.");
391 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, NULL);
392 if (!SQL_SUCCEEDED(sr))
393 throw IOException("ODBC StorageService failed to set transaction isolation level.");
398 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
401 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
402 if (!SQL_SUCCEEDED(sr)) {
403 m_log.error("failed to allocate statement handle");
404 log_error(conn, SQL_HANDLE_DBC);
405 throw IOException("ODBC StorageService failed to allocate a statement handle.");
410 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
412 // Grab the version number from the database.
413 SQLHSTMT stmt = getHSTMT(conn);
415 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
416 if (!SQL_SUCCEEDED(sr)) {
417 m_log.error("failed to read version from database");
418 log_error(stmt, SQL_HANDLE_STMT);
419 throw IOException("ODBC StorageService failed to read version from database.");
424 SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,NULL);
425 SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,NULL);
427 if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
428 return pair<int,int>(major,minor);
430 m_log.error("no rows returned in version query");
431 throw IOException("ODBC StorageService failed to read version from database.");
434 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
437 xmltooling::NDC ndc("createRow");
441 timestampFromTime(expiration, timebuf);
443 // Get statement handle.
444 ODBCConn conn(getHDBC());
445 SQLHSTMT stmt = getHSTMT(conn);
447 // Prepare and exectute insert statement.
448 //char *scontext = makeSafeSQL(context);
449 //char *skey = makeSafeSQL(key);
450 //char *svalue = makeSafeSQL(value);
451 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
453 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
454 if (!SQL_SUCCEEDED(sr)) {
455 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
456 log_error(stmt, SQL_HANDLE_STMT);
457 throw IOException("ODBC StorageService failed to insert record.");
459 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
461 SQLLEN b_ind = SQL_NTS;
462 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
463 if (!SQL_SUCCEEDED(sr)) {
464 m_log.error("SQLBindParam failed (context = %s)", context);
465 log_error(stmt, SQL_HANDLE_STMT);
466 throw IOException("ODBC StorageService failed to insert record.");
468 m_log.debug("SQLBindParam succeeded (context = %s)", context);
470 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
471 if (!SQL_SUCCEEDED(sr)) {
472 m_log.error("SQLBindParam failed (key = %s)", key);
473 log_error(stmt, SQL_HANDLE_STMT);
474 throw IOException("ODBC StorageService failed to insert record.");
476 m_log.debug("SQLBindParam succeeded (key = %s)", key);
478 if (strcmp(table, TEXT_TABLE)==0)
479 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
481 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
482 if (!SQL_SUCCEEDED(sr)) {
483 m_log.error("SQLBindParam failed (value = %s)", value);
484 log_error(stmt, SQL_HANDLE_STMT);
485 throw IOException("ODBC StorageService failed to insert record.");
487 m_log.debug("SQLBindParam succeeded (value = %s)", value);
489 //freeSafeSQL(scontext, context);
490 //freeSafeSQL(skey, key);
491 //freeSafeSQL(svalue, value);
492 //m_log.debug("SQL: %s", q.c_str());
495 pair<bool,bool> logres;
497 logres = make_pair(false,false);
500 if (SQL_SUCCEEDED(sr)) {
501 m_log.debug("SQLExecute of insert succeeded");
504 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
505 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
507 return false; // supposedly integrity violation?
508 } while (attempts && logres.first);
510 throw IOException("ODBC StorageService failed to insert record.");
513 int ODBCStorageService::readRow(
514 const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
518 xmltooling::NDC ndc("readRow");
521 // Get statement handle.
522 ODBCConn conn(getHDBC());
523 SQLHSTMT stmt = getHSTMT(conn);
525 // Prepare and exectute select statement.
527 timestampFromTime(time(NULL), timebuf);
528 char *scontext = makeSafeSQL(context);
529 char *skey = makeSafeSQL(key);
531 q << "SELECT version";
535 q << ",CASE version WHEN " << version << " THEN NULL ELSE value END";
536 q << " FROM " << table << " WHERE context='" << scontext << "' AND id='" << skey << "' AND expires > " << timebuf;
537 freeSafeSQL(scontext, context);
538 freeSafeSQL(skey, key);
539 if (m_log.isDebugEnabled())
540 m_log.debug("SQL: %s", q.str().c_str());
542 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
543 if (!SQL_SUCCEEDED(sr)) {
544 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
545 log_error(stmt, SQL_HANDLE_STMT);
546 throw IOException("ODBC StorageService search failed.");
550 SQL_TIMESTAMP_STRUCT expiration;
552 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
554 SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,NULL);
556 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
560 *pexpiration = timeFromTimestamp(expiration);
563 return version; // nothing's changed, so just echo back the version
567 SQLCHAR buf[LONGDATA_BUFLEN];
568 while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
569 if (!SQL_SUCCEEDED(sr)) {
570 m_log.error("error while reading text field from result set");
571 log_error(stmt, SQL_HANDLE_STMT);
572 throw IOException("ODBC StorageService search failed to read data from result set.");
574 pvalue->append((char*)buf);
581 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
584 xmltooling::NDC ndc("updateRow");
587 if (!value && !expiration)
588 throw IOException("ODBC StorageService given invalid update instructions.");
590 // Get statement handle. Disable auto-commit mode to wrap select + update.
591 ODBCConn conn(getHDBC());
592 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, NULL);
593 if (!SQL_SUCCEEDED(sr))
594 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
595 conn.autoCommit = false;
596 SQLHSTMT stmt = getHSTMT(conn);
598 // First, fetch the current version for later, which also ensures the record still exists.
600 timestampFromTime(time(NULL), timebuf);
601 char *scontext = makeSafeSQL(context);
602 char *skey = makeSafeSQL(key);
603 string q("SELECT version FROM ");
604 q = q + table + " WHERE context='" + scontext + "' AND id='" + skey + "' AND expires > " + timebuf;
606 m_log.debug("SQL: %s", q.c_str());
608 sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
609 if (!SQL_SUCCEEDED(sr)) {
610 freeSafeSQL(scontext, context);
611 freeSafeSQL(skey, key);
612 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
613 log_error(stmt, SQL_HANDLE_STMT);
614 throw IOException("ODBC StorageService search failed.");
618 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
619 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
620 freeSafeSQL(scontext, context);
621 freeSafeSQL(skey, key);
626 if (version > 0 && version != ver) {
627 freeSafeSQL(scontext, context);
628 freeSafeSQL(skey, key);
632 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
633 stmt = getHSTMT(conn);
635 // Prepare and exectute update statement.
636 q = string("UPDATE ") + table + " SET ";
639 q = q + "value=?, version=version+1";
642 timestampFromTime(expiration, timebuf);
645 q = q + "expires = " + timebuf;
648 q = q + " WHERE context='" + scontext + "' AND id='" + skey + "'";
649 freeSafeSQL(scontext, context);
650 freeSafeSQL(skey, key);
652 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
653 if (!SQL_SUCCEEDED(sr)) {
654 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
655 log_error(stmt, SQL_HANDLE_STMT);
656 throw IOException("ODBC StorageService failed to update record.");
658 m_log.debug("SQLPrepare succeeded. SQL: %s", q.c_str());
660 SQLLEN b_ind = SQL_NTS;
662 if (strcmp(table, TEXT_TABLE)==0)
663 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
665 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
666 if (!SQL_SUCCEEDED(sr)) {
667 m_log.error("SQLBindParam failed (context = %s)", context);
668 log_error(stmt, SQL_HANDLE_STMT);
669 throw IOException("ODBC StorageService failed to update record.");
671 m_log.debug("SQLBindParam succeeded (context = %s)", context);
675 pair<bool,bool> logres;
677 logres = make_pair(false,false);
681 return 0; // went missing?
682 else if (SQL_SUCCEEDED(sr)) {
683 m_log.debug("SQLExecute of update succeeded");
687 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
688 logres = log_error(stmt, SQL_HANDLE_STMT);
689 } while (attempts && logres.first);
691 throw IOException("ODBC StorageService failed to update record.");
694 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
697 xmltooling::NDC ndc("deleteRow");
700 // Get statement handle.
701 ODBCConn conn(getHDBC());
702 SQLHSTMT stmt = getHSTMT(conn);
704 // Prepare and execute delete statement.
705 char *scontext = makeSafeSQL(context);
706 char *skey = makeSafeSQL(key);
707 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND id='" + skey + "'";
708 freeSafeSQL(scontext, context);
709 freeSafeSQL(skey, key);
710 m_log.debug("SQL: %s", q.c_str());
712 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
715 else if (!SQL_SUCCEEDED(sr)) {
716 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
717 log_error(stmt, SQL_HANDLE_STMT);
718 throw IOException("ODBC StorageService failed to delete record.");
725 void ODBCStorageService::cleanup()
728 xmltooling::NDC ndc("cleanup");
731 Mutex* mutex = Mutex::create();
735 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
738 shutdown_wait->timedwait(mutex, m_cleanupInterval);
744 catch (exception& ex) {
745 m_log.error("cleanup thread swallowed exception: %s", ex.what());
749 m_log.info("cleanup thread exiting...");
756 void* ODBCStorageService::cleanup_fn(void* cache_p)
758 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
761 // First, let's block all signals
762 Thread::mask_all_signals();
765 // Now run the cleanup process.
770 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
773 xmltooling::NDC ndc("updateContext");
776 // Get statement handle.
777 ODBCConn conn(getHDBC());
778 SQLHSTMT stmt = getHSTMT(conn);
781 timestampFromTime(expiration, timebuf);
784 timestampFromTime(time(NULL), nowbuf);
786 char *scontext = makeSafeSQL(context);
788 q = q + table + " SET expires = " + timebuf + " WHERE context='" + scontext + "' AND expires > " + nowbuf;
789 freeSafeSQL(scontext, context);
791 m_log.debug("SQL: %s", q.c_str());
793 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
794 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
795 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
796 log_error(stmt, SQL_HANDLE_STMT);
797 throw IOException("ODBC StorageService failed to update context expiration.");
801 void ODBCStorageService::reap(const char *table, const char* context)
804 xmltooling::NDC ndc("reap");
807 // Get statement handle.
808 ODBCConn conn(getHDBC());
809 SQLHSTMT stmt = getHSTMT(conn);
811 // Prepare and execute delete statement.
813 timestampFromTime(time(NULL), nowbuf);
816 char *scontext = makeSafeSQL(context);
817 q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= " + nowbuf;
818 freeSafeSQL(scontext, context);
821 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
823 m_log.debug("SQL: %s", q.c_str());
825 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
826 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
827 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
828 log_error(stmt, SQL_HANDLE_STMT);
829 throw IOException("ODBC StorageService failed to purge expired records.");
833 void ODBCStorageService::deleteContext(const char *table, const char* context)
836 xmltooling::NDC ndc("deleteContext");
839 // Get statement handle.
840 ODBCConn conn(getHDBC());
841 SQLHSTMT stmt = getHSTMT(conn);
843 // Prepare and execute delete statement.
844 char *scontext = makeSafeSQL(context);
845 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
846 freeSafeSQL(scontext, context);
847 m_log.debug("SQL: %s", q.c_str());
849 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
850 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
851 m_log.error("error deleting context (t=%s, c=%s)", table, context);
852 log_error(stmt, SQL_HANDLE_STMT);
853 throw IOException("ODBC StorageService failed to delete context.");
857 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
859 // Register this SS type
860 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
864 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
866 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");