2 * Copyright 2001-2007 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/XMLToolingConfig.h>
43 #include <xmltooling/util/NDC.h>
44 #include <xmltooling/util/StorageService.h>
45 #include <xmltooling/util/Threads.h>
46 #include <xmltooling/util/XMLHelper.h>
51 using namespace xmltooling::logging;
52 using namespace xmltooling;
53 using namespace xercesc;
56 #define PLUGIN_VER_MAJOR 1
57 #define PLUGIN_VER_MINOR 0
59 #define LONGDATA_BUFLEN 16384
61 #define COLSIZE_CONTEXT 255
62 #define COLSIZE_ID 255
63 #define COLSIZE_STRING_VALUE 255
65 #define STRING_TABLE "strings"
66 #define TEXT_TABLE "texts"
69 CREATE TABLE version (
70 major tinyint NOT NULL,
71 minor tinyint NOT NULL
74 CREATE TABLE strings (
75 context varchar(255) not null,
76 id varchar(255) not null,
77 expires datetime not null,
78 version smallint not null,
79 value varchar(255) not null,
80 PRIMARY KEY (context, id)
84 context varchar(255) not null,
85 id varchar(255) not null,
86 expires datetime not null,
87 version smallint not null,
89 PRIMARY KEY (context, id)
94 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
95 static const XMLCh isolationLevel[] = UNICODE_LITERAL_14(i,s,o,l,a,t,i,o,n,L,e,v,e,l);
96 static const XMLCh ConnectionString[] = UNICODE_LITERAL_16(C,o,n,n,e,c,t,i,o,n,S,t,r,i,n,g);
98 // RAII for ODBC handles
100 ODBCConn(SQLHDBC conn) : handle(conn), autoCommit(true) {}
102 SQLRETURN sr = SQL_SUCCESS;
104 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, NULL);
105 SQLDisconnect(handle);
106 SQLFreeHandle(SQL_HANDLE_DBC,handle);
107 if (!SQL_SUCCEEDED(sr))
108 throw IOException("Failed to commit connection and return to auto-commit mode.");
110 operator SQLHDBC() {return handle;}
115 class ODBCStorageService : public StorageService
118 ODBCStorageService(const DOMElement* e);
119 virtual ~ODBCStorageService();
121 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
122 return createRow(STRING_TABLE, context, key, value, expiration);
124 int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
125 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
127 int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
128 return updateRow(STRING_TABLE, context, key, value, expiration, version);
130 bool deleteString(const char* context, const char* key) {
131 return deleteRow(STRING_TABLE, context, key);
134 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
135 return createRow(TEXT_TABLE, context, key, value, expiration);
137 int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
138 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
140 int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
141 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
143 bool deleteText(const char* context, const char* key) {
144 return deleteRow(TEXT_TABLE, context, key);
147 void reap(const char* context) {
148 reap(STRING_TABLE, context);
149 reap(TEXT_TABLE, context);
152 void updateContext(const char* context, time_t expiration) {
153 updateContext(STRING_TABLE, context, expiration);
154 updateContext(TEXT_TABLE, context, expiration);
157 void deleteContext(const char* context) {
158 deleteContext(STRING_TABLE, context);
159 deleteContext(TEXT_TABLE, context);
164 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
165 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
166 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
167 bool deleteRow(const char *table, const char* context, const char* key);
169 void reap(const char* table, const char* context);
170 void updateContext(const char* table, const char* context, time_t expiration);
171 void deleteContext(const char* table, const char* context);
174 SQLHSTMT getHSTMT(SQLHDBC);
175 pair<int,int> getVersion(SQLHDBC);
176 bool log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=NULL);
178 static void* cleanup_fn(void*);
182 int m_cleanupInterval;
183 CondWait* shutdown_wait;
184 Thread* cleanup_thread;
192 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
194 return new ODBCStorageService(e);
197 // convert SQL timestamp to time_t
198 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
202 t.tm_sec=expires.second;
203 t.tm_min=expires.minute;
204 t.tm_hour=expires.hour;
205 t.tm_mday=expires.day;
206 t.tm_mon=expires.month-1;
207 t.tm_year=expires.year-1900;
209 #if defined(HAVE_TIMEGM)
212 ret = mktime(&t) - timezone;
217 // conver time_t to SQL string
218 void timestampFromTime(time_t t, char* ret)
222 struct tm* ptime=gmtime_r(&t,&res);
224 struct tm* ptime=gmtime(&t);
226 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
229 // make a string safe for SQL command
230 // result to be free'd only if it isn't the input
231 static char *makeSafeSQL(const char *src)
237 // see if any conversion needed
238 for (s=(char*)src; *s; nc++,s++) if (*s=='\'') ns++;
239 if (ns==0) return ((char*)src);
241 char *safe = new char[(nc+2*ns+1)];
242 for (s=safe; *src; src++) {
243 if (*src=='\'') *s++ = '\'';
250 void freeSafeSQL(char *safe, const char *src)
257 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
258 m_cleanupInterval(900), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_henv(SQL_NULL_HANDLE), m_isolation(SQL_TXN_SERIALIZABLE)
261 xmltooling::NDC ndc("ODBCStorageService");
264 const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
266 m_cleanupInterval = XMLString::parseInt(tag);
267 if (!m_cleanupInterval)
268 m_cleanupInterval = 900;
270 auto_ptr_char iso(e ? e->getAttributeNS(NULL,isolationLevel) : NULL);
271 if (iso.get() && *iso.get()) {
272 if (!strcmp(iso.get(),"SERIALIZABLE"))
273 m_isolation = SQL_TXN_SERIALIZABLE;
274 else if (!strcmp(iso.get(),"REPEATABLE_READ"))
275 m_isolation = SQL_TXN_REPEATABLE_READ;
276 else if (!strcmp(iso.get(),"READ_COMMITTED"))
277 m_isolation = SQL_TXN_READ_COMMITTED;
278 else if (!strcmp(iso.get(),"READ_UNCOMMITTED"))
279 m_isolation = SQL_TXN_READ_UNCOMMITTED;
281 throw XMLToolingException("Unknown transaction isolationLevel property.");
284 if (m_henv == SQL_NULL_HANDLE) {
285 // Enable connection pooling.
286 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
288 // Allocate the environment.
289 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
290 throw XMLToolingException("ODBC failed to initialize.");
293 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
295 m_log.info("ODBC initialized");
298 // Grab connection string from the configuration.
299 e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : NULL;
300 if (!e || !e->hasChildNodes()) {
301 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
302 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
304 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
305 m_connstring=arg.get();
307 // Connect and check version.
308 ODBCConn conn(getHDBC());
309 pair<int,int> v=getVersion(conn);
311 // Make sure we've got the right version.
312 if (v.first != PLUGIN_VER_MAJOR) {
313 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
314 m_log.crit("unknown database version: %d.%d", v.first, v.second);
315 throw XMLToolingException("Unknown database version for ODBC StorageService.");
318 // Initialize the cleanup thread
319 shutdown_wait = CondWait::create();
320 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
323 ODBCStorageService::~ODBCStorageService()
326 shutdown_wait->signal();
327 cleanup_thread->join(NULL);
328 delete shutdown_wait;
329 if (m_henv != SQL_NULL_HANDLE)
330 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
333 bool ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
344 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
345 if (SQL_SUCCEEDED(ret)) {
346 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
347 if (checkfor && !strcmp(checkfor, (const char*)state))
350 } while(SQL_SUCCEEDED(ret));
354 SQLHDBC ODBCStorageService::getHDBC()
357 xmltooling::NDC ndc("getHDBC");
362 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
363 if (!SQL_SUCCEEDED(sr)) {
364 m_log.error("failed to allocate connection handle");
365 log_error(m_henv, SQL_HANDLE_ENV);
366 throw IOException("ODBC StorageService failed to allocate a connection handle.");
369 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
370 if (!SQL_SUCCEEDED(sr)) {
371 m_log.error("failed to connect to database");
372 log_error(handle, SQL_HANDLE_DBC);
373 throw IOException("ODBC StorageService failed to connect to database.");
376 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, NULL);
377 if (!SQL_SUCCEEDED(sr))
378 throw IOException("ODBC StorageService failed to set transaction isolation level.");
383 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
386 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
387 if (!SQL_SUCCEEDED(sr)) {
388 m_log.error("failed to allocate statement handle");
389 log_error(conn, SQL_HANDLE_DBC);
390 throw IOException("ODBC StorageService failed to allocate a statement handle.");
395 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
397 // Grab the version number from the database.
398 SQLHSTMT stmt = getHSTMT(conn);
400 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
401 if (!SQL_SUCCEEDED(sr)) {
402 m_log.error("failed to read version from database");
403 log_error(stmt, SQL_HANDLE_STMT);
404 throw IOException("ODBC StorageService failed to read version from database.");
409 SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,NULL);
410 SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,NULL);
412 if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
413 return pair<int,int>(major,minor);
415 m_log.error("no rows returned in version query");
416 throw IOException("ODBC StorageService failed to read version from database.");
419 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
422 xmltooling::NDC ndc("createRow");
426 timestampFromTime(expiration, timebuf);
428 // Get statement handle.
429 ODBCConn conn(getHDBC());
430 SQLHSTMT stmt = getHSTMT(conn);
432 // Prepare and exectute insert statement.
433 //char *scontext = makeSafeSQL(context);
434 //char *skey = makeSafeSQL(key);
435 //char *svalue = makeSafeSQL(value);
436 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
438 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
439 if (!SQL_SUCCEEDED(sr)) {
440 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
441 log_error(stmt, SQL_HANDLE_STMT);
442 throw IOException("ODBC StorageService failed to insert record.");
444 m_log.debug("SQLPrepare succeded. SQL: %s", q.c_str());
446 SQLINTEGER b_ind = SQL_NTS;
447 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
448 if (!SQL_SUCCEEDED(sr)) {
449 m_log.error("SQLBindParam failed (context = %s)", context);
450 log_error(stmt, SQL_HANDLE_STMT);
451 throw IOException("ODBC StorageService failed to insert record.");
453 m_log.debug("SQLBindParam succeded (context = %s)", context);
455 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
456 if (!SQL_SUCCEEDED(sr)) {
457 m_log.error("SQLBindParam failed (key = %s)", key);
458 log_error(stmt, SQL_HANDLE_STMT);
459 throw IOException("ODBC StorageService failed to insert record.");
461 m_log.debug("SQLBindParam succeded (key = %s)", key);
463 if (strcmp(table, TEXT_TABLE)==0)
464 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
466 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
467 if (!SQL_SUCCEEDED(sr)) {
468 m_log.error("SQLBindParam failed (value = %s)", value);
469 log_error(stmt, SQL_HANDLE_STMT);
470 throw IOException("ODBC StorageService failed to insert record.");
472 m_log.debug("SQLBindParam succeded (value = %s)", value);
474 //freeSafeSQL(scontext, context);
475 //freeSafeSQL(skey, key);
476 //freeSafeSQL(svalue, value);
477 //m_log.debug("SQL: %s", q.c_str());
480 if (!SQL_SUCCEEDED(sr)) {
481 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
482 if (log_error(stmt, SQL_HANDLE_STMT, "23000"))
483 return false; // supposedly integrity violation?
484 throw IOException("ODBC StorageService failed to insert record.");
487 m_log.debug("SQLExecute of insert succeeded");
491 int ODBCStorageService::readRow(
492 const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
496 xmltooling::NDC ndc("readRow");
499 // Get statement handle.
500 ODBCConn conn(getHDBC());
501 SQLHSTMT stmt = getHSTMT(conn);
503 // Prepare and exectute select statement.
505 timestampFromTime(time(NULL), timebuf);
506 char *scontext = makeSafeSQL(context);
507 char *skey = makeSafeSQL(key);
509 q << "SELECT version";
513 q << ",CASE version WHEN " << version << " THEN NULL ELSE value END";
514 q << " FROM " << table << " WHERE context='" << scontext << "' AND id='" << skey << "' AND expires > " << timebuf;
515 freeSafeSQL(scontext, context);
516 freeSafeSQL(skey, key);
517 if (m_log.isDebugEnabled())
518 m_log.debug("SQL: %s", q.str().c_str());
520 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
521 if (!SQL_SUCCEEDED(sr)) {
522 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
523 log_error(stmt, SQL_HANDLE_STMT);
524 throw IOException("ODBC StorageService search failed.");
528 SQL_TIMESTAMP_STRUCT expiration;
530 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
532 SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,NULL);
534 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
538 *pexpiration = timeFromTimestamp(expiration);
541 return version; // nothing's changed, so just echo back the version
545 SQLCHAR buf[LONGDATA_BUFLEN];
546 while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
547 if (!SQL_SUCCEEDED(sr)) {
548 m_log.error("error while reading text field from result set");
549 log_error(stmt, SQL_HANDLE_STMT);
550 throw IOException("ODBC StorageService search failed to read data from result set.");
552 pvalue->append((char*)buf);
559 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
562 xmltooling::NDC ndc("updateRow");
565 if (!value && !expiration)
566 throw IOException("ODBC StorageService given invalid update instructions.");
568 // Get statement handle. Disable auto-commit mode to wrap select + update.
569 ODBCConn conn(getHDBC());
570 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, NULL);
571 if (!SQL_SUCCEEDED(sr))
572 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
573 conn.autoCommit = false;
574 SQLHSTMT stmt = getHSTMT(conn);
576 // First, fetch the current version for later, which also ensures the record still exists.
578 timestampFromTime(time(NULL), timebuf);
579 char *scontext = makeSafeSQL(context);
580 char *skey = makeSafeSQL(key);
581 string q("SELECT version FROM ");
582 q = q + table + " WHERE context='" + scontext + "' AND id='" + skey + "' AND expires > " + timebuf;
584 m_log.debug("SQL: %s", q.c_str());
586 sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
587 if (!SQL_SUCCEEDED(sr)) {
588 freeSafeSQL(scontext, context);
589 freeSafeSQL(skey, key);
590 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
591 log_error(stmt, SQL_HANDLE_STMT);
592 throw IOException("ODBC StorageService search failed.");
596 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
597 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
598 freeSafeSQL(scontext, context);
599 freeSafeSQL(skey, key);
604 if (version > 0 && version != ver) {
605 freeSafeSQL(scontext, context);
606 freeSafeSQL(skey, key);
610 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
611 stmt = getHSTMT(conn);
613 // Prepare and exectute update statement.
614 q = string("UPDATE ") + table + " SET ";
617 q = q + "value=?, version=version+1";
620 timestampFromTime(expiration, timebuf);
623 q = q + "expires = " + timebuf;
626 q = q + " WHERE context='" + scontext + "' AND id='" + skey + "'";
627 freeSafeSQL(scontext, context);
628 freeSafeSQL(skey, key);
630 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
631 if (!SQL_SUCCEEDED(sr)) {
632 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
633 log_error(stmt, SQL_HANDLE_STMT);
634 throw IOException("ODBC StorageService failed to update record.");
636 m_log.debug("SQLPrepare succeded. SQL: %s", q.c_str());
638 SQLINTEGER b_ind = SQL_NTS;
640 if (strcmp(table, TEXT_TABLE)==0)
641 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
643 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
644 if (!SQL_SUCCEEDED(sr)) {
645 m_log.error("SQLBindParam failed (context = %s)", context);
646 log_error(stmt, SQL_HANDLE_STMT);
647 throw IOException("ODBC StorageService failed to update record.");
649 m_log.debug("SQLBindParam succeded (context = %s)", context);
654 return 0; // went missing?
655 else if (!SQL_SUCCEEDED(sr)) {
656 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
657 log_error(stmt, SQL_HANDLE_STMT);
658 throw IOException("ODBC StorageService failed to update record.");
661 m_log.debug("SQLExecute of update succeeded");
665 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
668 xmltooling::NDC ndc("deleteRow");
671 // Get statement handle.
672 ODBCConn conn(getHDBC());
673 SQLHSTMT stmt = getHSTMT(conn);
675 // Prepare and execute delete statement.
676 char *scontext = makeSafeSQL(context);
677 char *skey = makeSafeSQL(key);
678 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND id='" + skey + "'";
679 freeSafeSQL(scontext, context);
680 freeSafeSQL(skey, key);
681 m_log.debug("SQL: %s", q.c_str());
683 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
686 else if (!SQL_SUCCEEDED(sr)) {
687 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
688 log_error(stmt, SQL_HANDLE_STMT);
689 throw IOException("ODBC StorageService failed to delete record.");
696 void ODBCStorageService::cleanup()
699 xmltooling::NDC ndc("cleanup");
702 Mutex* mutex = Mutex::create();
706 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
709 shutdown_wait->timedwait(mutex, m_cleanupInterval);
715 catch (exception& ex) {
716 m_log.error("cleanup thread swallowed exception: %s", ex.what());
720 m_log.info("cleanup thread exiting...");
727 void* ODBCStorageService::cleanup_fn(void* cache_p)
729 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
732 // First, let's block all signals
733 Thread::mask_all_signals();
736 // Now run the cleanup process.
741 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
744 xmltooling::NDC ndc("updateContext");
747 // Get statement handle.
748 ODBCConn conn(getHDBC());
749 SQLHSTMT stmt = getHSTMT(conn);
752 timestampFromTime(expiration, timebuf);
755 timestampFromTime(time(NULL), nowbuf);
757 char *scontext = makeSafeSQL(context);
759 q = q + table + " SET expires = " + timebuf + " WHERE context='" + scontext + "' AND expires > " + nowbuf;
760 freeSafeSQL(scontext, context);
762 m_log.debug("SQL: %s", q.c_str());
764 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
765 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
766 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
767 log_error(stmt, SQL_HANDLE_STMT);
768 throw IOException("ODBC StorageService failed to update context expiration.");
772 void ODBCStorageService::reap(const char *table, const char* context)
775 xmltooling::NDC ndc("reap");
778 // Get statement handle.
779 ODBCConn conn(getHDBC());
780 SQLHSTMT stmt = getHSTMT(conn);
782 // Prepare and execute delete statement.
784 timestampFromTime(time(NULL), nowbuf);
787 char *scontext = makeSafeSQL(context);
788 q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= " + nowbuf;
789 freeSafeSQL(scontext, context);
792 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
794 m_log.debug("SQL: %s", q.c_str());
796 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
797 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
798 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
799 log_error(stmt, SQL_HANDLE_STMT);
800 throw IOException("ODBC StorageService failed to purge expired records.");
804 void ODBCStorageService::deleteContext(const char *table, const char* context)
807 xmltooling::NDC ndc("deleteContext");
810 // Get statement handle.
811 ODBCConn conn(getHDBC());
812 SQLHSTMT stmt = getHSTMT(conn);
814 // Prepare and execute delete statement.
815 char *scontext = makeSafeSQL(context);
816 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
817 freeSafeSQL(scontext, context);
818 m_log.debug("SQL: %s", q.c_str());
820 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
821 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
822 m_log.error("error deleting context (t=%s, c=%s)", table, context);
823 log_error(stmt, SQL_HANDLE_STMT);
824 throw IOException("ODBC StorageService failed to delete context.");
828 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
830 // Register this SS type
831 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
835 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
837 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");