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);
97 static const XMLCh RetryOnError[] = UNICODE_LITERAL_12(R,e,t,r,y,O,n,E,r,r,o,r);
99 // RAII for ODBC handles
101 ODBCConn(SQLHDBC conn) : handle(conn), autoCommit(true) {}
103 SQLRETURN sr = SQL_SUCCESS;
105 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, NULL);
106 SQLDisconnect(handle);
107 SQLFreeHandle(SQL_HANDLE_DBC,handle);
108 if (!SQL_SUCCEEDED(sr))
109 throw IOException("Failed to commit connection and return to auto-commit mode.");
111 operator SQLHDBC() {return handle;}
116 class ODBCStorageService : public StorageService
119 ODBCStorageService(const DOMElement* e);
120 virtual ~ODBCStorageService();
122 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
123 return createRow(STRING_TABLE, context, key, value, expiration);
125 int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
126 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
128 int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
129 return updateRow(STRING_TABLE, context, key, value, expiration, version);
131 bool deleteString(const char* context, const char* key) {
132 return deleteRow(STRING_TABLE, context, key);
135 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
136 return createRow(TEXT_TABLE, context, key, value, expiration);
138 int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
139 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
141 int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
142 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
144 bool deleteText(const char* context, const char* key) {
145 return deleteRow(TEXT_TABLE, context, key);
148 void reap(const char* context) {
149 reap(STRING_TABLE, context);
150 reap(TEXT_TABLE, context);
153 void updateContext(const char* context, time_t expiration) {
154 updateContext(STRING_TABLE, context, expiration);
155 updateContext(TEXT_TABLE, context, expiration);
158 void deleteContext(const char* context) {
159 deleteContext(STRING_TABLE, context);
160 deleteContext(TEXT_TABLE, context);
165 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
166 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
167 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
168 bool deleteRow(const char *table, const char* context, const char* key);
170 void reap(const char* table, const char* context);
171 void updateContext(const char* table, const char* context, time_t expiration);
172 void deleteContext(const char* table, const char* context);
175 SQLHSTMT getHSTMT(SQLHDBC);
176 pair<int,int> getVersion(SQLHDBC);
177 pair<bool,bool> log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=NULL);
179 static void* cleanup_fn(void*);
183 int m_cleanupInterval;
184 CondWait* shutdown_wait;
185 Thread* cleanup_thread;
191 vector<SQLINTEGER> m_retries;
194 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
196 return new ODBCStorageService(e);
199 // convert SQL timestamp to time_t
200 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
204 t.tm_sec=expires.second;
205 t.tm_min=expires.minute;
206 t.tm_hour=expires.hour;
207 t.tm_mday=expires.day;
208 t.tm_mon=expires.month-1;
209 t.tm_year=expires.year-1900;
211 #if defined(HAVE_TIMEGM)
214 ret = mktime(&t) - timezone;
219 // conver time_t to SQL string
220 void timestampFromTime(time_t t, char* ret)
224 struct tm* ptime=gmtime_r(&t,&res);
226 struct tm* ptime=gmtime(&t);
228 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
231 // make a string safe for SQL command
232 // result to be free'd only if it isn't the input
233 static char *makeSafeSQL(const char *src)
239 // see if any conversion needed
240 for (s=(char*)src; *s; nc++,s++) if (*s=='\'') ns++;
241 if (ns==0) return ((char*)src);
243 char *safe = new char[(nc+2*ns+1)];
244 for (s=safe; *src; src++) {
245 if (*src=='\'') *s++ = '\'';
252 void freeSafeSQL(char *safe, const char *src)
259 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
260 m_cleanupInterval(900), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_henv(SQL_NULL_HANDLE), m_isolation(SQL_TXN_SERIALIZABLE)
263 xmltooling::NDC ndc("ODBCStorageService");
266 const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
268 m_cleanupInterval = XMLString::parseInt(tag);
269 if (!m_cleanupInterval)
270 m_cleanupInterval = 900;
272 auto_ptr_char iso(e ? e->getAttributeNS(NULL,isolationLevel) : NULL);
273 if (iso.get() && *iso.get()) {
274 if (!strcmp(iso.get(),"SERIALIZABLE"))
275 m_isolation = SQL_TXN_SERIALIZABLE;
276 else if (!strcmp(iso.get(),"REPEATABLE_READ"))
277 m_isolation = SQL_TXN_REPEATABLE_READ;
278 else if (!strcmp(iso.get(),"READ_COMMITTED"))
279 m_isolation = SQL_TXN_READ_COMMITTED;
280 else if (!strcmp(iso.get(),"READ_UNCOMMITTED"))
281 m_isolation = SQL_TXN_READ_UNCOMMITTED;
283 throw XMLToolingException("Unknown transaction isolationLevel property.");
286 if (m_henv == SQL_NULL_HANDLE) {
287 // Enable connection pooling.
288 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
290 // Allocate the environment.
291 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
292 throw XMLToolingException("ODBC failed to initialize.");
295 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
297 m_log.info("ODBC initialized");
300 // Grab connection string from the configuration.
301 e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : NULL;
302 if (!e || !e->hasChildNodes()) {
303 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
304 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
306 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
307 m_connstring=arg.get();
309 // Connect and check version.
310 ODBCConn conn(getHDBC());
311 pair<int,int> v=getVersion(conn);
313 // Make sure we've got the right version.
314 if (v.first != PLUGIN_VER_MAJOR) {
315 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
316 m_log.crit("unknown database version: %d.%d", v.first, v.second);
317 throw XMLToolingException("Unknown database version for ODBC StorageService.");
320 // Load any retry errors to check.
321 e = XMLHelper::getNextSiblingElement(e,RetryOnError);
323 if (e->hasChildNodes()) {
324 m_retries.push_back(XMLString::parseInt(e->getFirstChild()->getNodeValue()));
325 m_log.info("will retry operations when native ODBC error (%ld) is returned", m_retries.back());
327 e = XMLHelper::getNextSiblingElement(e,RetryOnError);
330 // Initialize the cleanup thread
331 shutdown_wait = CondWait::create();
332 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
335 ODBCStorageService::~ODBCStorageService()
338 shutdown_wait->signal();
339 cleanup_thread->join(NULL);
340 delete shutdown_wait;
341 if (m_henv != SQL_NULL_HANDLE)
342 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
345 pair<bool,bool> ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
354 pair<bool,bool> res = make_pair(false,false);
356 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
357 if (SQL_SUCCEEDED(ret)) {
358 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
359 for (vector<SQLINTEGER>::const_iterator n = m_retries.begin(); !res.first && n != m_retries.end(); ++n)
360 res.first = (*n == native);
361 if (checkfor && !strcmp(checkfor, (const char*)state))
364 } while(SQL_SUCCEEDED(ret));
368 SQLHDBC ODBCStorageService::getHDBC()
371 xmltooling::NDC ndc("getHDBC");
376 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
377 if (!SQL_SUCCEEDED(sr)) {
378 m_log.error("failed to allocate connection handle");
379 log_error(m_henv, SQL_HANDLE_ENV);
380 throw IOException("ODBC StorageService failed to allocate a connection handle.");
383 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
384 if (!SQL_SUCCEEDED(sr)) {
385 m_log.error("failed to connect to database");
386 log_error(handle, SQL_HANDLE_DBC);
387 throw IOException("ODBC StorageService failed to connect to database.");
390 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)m_isolation, NULL);
391 if (!SQL_SUCCEEDED(sr))
392 throw IOException("ODBC StorageService failed to set transaction isolation level.");
397 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
400 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
401 if (!SQL_SUCCEEDED(sr)) {
402 m_log.error("failed to allocate statement handle");
403 log_error(conn, SQL_HANDLE_DBC);
404 throw IOException("ODBC StorageService failed to allocate a statement handle.");
409 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
411 // Grab the version number from the database.
412 SQLHSTMT stmt = getHSTMT(conn);
414 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
415 if (!SQL_SUCCEEDED(sr)) {
416 m_log.error("failed to read version from database");
417 log_error(stmt, SQL_HANDLE_STMT);
418 throw IOException("ODBC StorageService failed to read version from database.");
423 SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,NULL);
424 SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,NULL);
426 if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
427 return pair<int,int>(major,minor);
429 m_log.error("no rows returned in version query");
430 throw IOException("ODBC StorageService failed to read version from database.");
433 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
436 xmltooling::NDC ndc("createRow");
440 timestampFromTime(expiration, timebuf);
442 // Get statement handle.
443 ODBCConn conn(getHDBC());
444 SQLHSTMT stmt = getHSTMT(conn);
446 // Prepare and exectute insert statement.
447 //char *scontext = makeSafeSQL(context);
448 //char *skey = makeSafeSQL(key);
449 //char *svalue = makeSafeSQL(value);
450 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
452 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
453 if (!SQL_SUCCEEDED(sr)) {
454 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
455 log_error(stmt, SQL_HANDLE_STMT);
456 throw IOException("ODBC StorageService failed to insert record.");
458 m_log.debug("SQLPrepare succeded. SQL: %s", q.c_str());
460 SQLINTEGER b_ind = SQL_NTS;
461 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(context), &b_ind);
462 if (!SQL_SUCCEEDED(sr)) {
463 m_log.error("SQLBindParam failed (context = %s)", context);
464 log_error(stmt, SQL_HANDLE_STMT);
465 throw IOException("ODBC StorageService failed to insert record.");
467 m_log.debug("SQLBindParam succeded (context = %s)", context);
469 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(key), &b_ind);
470 if (!SQL_SUCCEEDED(sr)) {
471 m_log.error("SQLBindParam failed (key = %s)", key);
472 log_error(stmt, SQL_HANDLE_STMT);
473 throw IOException("ODBC StorageService failed to insert record.");
475 m_log.debug("SQLBindParam succeded (key = %s)", key);
477 if (strcmp(table, TEXT_TABLE)==0)
478 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
480 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
481 if (!SQL_SUCCEEDED(sr)) {
482 m_log.error("SQLBindParam failed (value = %s)", value);
483 log_error(stmt, SQL_HANDLE_STMT);
484 throw IOException("ODBC StorageService failed to insert record.");
486 m_log.debug("SQLBindParam succeded (value = %s)", value);
488 //freeSafeSQL(scontext, context);
489 //freeSafeSQL(skey, key);
490 //freeSafeSQL(svalue, value);
491 //m_log.debug("SQL: %s", q.c_str());
494 pair<bool,bool> logres;
496 logres = make_pair(false,false);
499 if (SQL_SUCCEEDED(sr)) {
500 m_log.debug("SQLExecute of insert succeeded");
503 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
504 logres = log_error(stmt, SQL_HANDLE_STMT, "23000");
506 return false; // supposedly integrity violation?
507 } while (attempts && logres.first);
509 throw IOException("ODBC StorageService failed to insert record.");
512 int ODBCStorageService::readRow(
513 const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
517 xmltooling::NDC ndc("readRow");
520 // Get statement handle.
521 ODBCConn conn(getHDBC());
522 SQLHSTMT stmt = getHSTMT(conn);
524 // Prepare and exectute select statement.
526 timestampFromTime(time(NULL), timebuf);
527 char *scontext = makeSafeSQL(context);
528 char *skey = makeSafeSQL(key);
530 q << "SELECT version";
534 q << ",CASE version WHEN " << version << " THEN NULL ELSE value END";
535 q << " FROM " << table << " WHERE context='" << scontext << "' AND id='" << skey << "' AND expires > " << timebuf;
536 freeSafeSQL(scontext, context);
537 freeSafeSQL(skey, key);
538 if (m_log.isDebugEnabled())
539 m_log.debug("SQL: %s", q.str().c_str());
541 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
542 if (!SQL_SUCCEEDED(sr)) {
543 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
544 log_error(stmt, SQL_HANDLE_STMT);
545 throw IOException("ODBC StorageService search failed.");
549 SQL_TIMESTAMP_STRUCT expiration;
551 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
553 SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,NULL);
555 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
559 *pexpiration = timeFromTimestamp(expiration);
562 return version; // nothing's changed, so just echo back the version
566 SQLCHAR buf[LONGDATA_BUFLEN];
567 while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
568 if (!SQL_SUCCEEDED(sr)) {
569 m_log.error("error while reading text field from result set");
570 log_error(stmt, SQL_HANDLE_STMT);
571 throw IOException("ODBC StorageService search failed to read data from result set.");
573 pvalue->append((char*)buf);
580 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
583 xmltooling::NDC ndc("updateRow");
586 if (!value && !expiration)
587 throw IOException("ODBC StorageService given invalid update instructions.");
589 // Get statement handle. Disable auto-commit mode to wrap select + update.
590 ODBCConn conn(getHDBC());
591 SQLRETURN sr = SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, NULL);
592 if (!SQL_SUCCEEDED(sr))
593 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
594 conn.autoCommit = false;
595 SQLHSTMT stmt = getHSTMT(conn);
597 // First, fetch the current version for later, which also ensures the record still exists.
599 timestampFromTime(time(NULL), timebuf);
600 char *scontext = makeSafeSQL(context);
601 char *skey = makeSafeSQL(key);
602 string q("SELECT version FROM ");
603 q = q + table + " WHERE context='" + scontext + "' AND id='" + skey + "' AND expires > " + timebuf;
605 m_log.debug("SQL: %s", q.c_str());
607 sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
608 if (!SQL_SUCCEEDED(sr)) {
609 freeSafeSQL(scontext, context);
610 freeSafeSQL(skey, key);
611 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
612 log_error(stmt, SQL_HANDLE_STMT);
613 throw IOException("ODBC StorageService search failed.");
617 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
618 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
619 freeSafeSQL(scontext, context);
620 freeSafeSQL(skey, key);
625 if (version > 0 && version != ver) {
626 freeSafeSQL(scontext, context);
627 freeSafeSQL(skey, key);
631 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
632 stmt = getHSTMT(conn);
634 // Prepare and exectute update statement.
635 q = string("UPDATE ") + table + " SET ";
638 q = q + "value=?, version=version+1";
641 timestampFromTime(expiration, timebuf);
644 q = q + "expires = " + timebuf;
647 q = q + " WHERE context='" + scontext + "' AND id='" + skey + "'";
648 freeSafeSQL(scontext, context);
649 freeSafeSQL(skey, key);
651 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
652 if (!SQL_SUCCEEDED(sr)) {
653 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
654 log_error(stmt, SQL_HANDLE_STMT);
655 throw IOException("ODBC StorageService failed to update record.");
657 m_log.debug("SQLPrepare succeded. SQL: %s", q.c_str());
659 SQLINTEGER b_ind = SQL_NTS;
661 if (strcmp(table, TEXT_TABLE)==0)
662 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_LONGVARCHAR, strlen(value), 0, const_cast<char*>(value), &b_ind);
664 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 255, 0, const_cast<char*>(value), &b_ind);
665 if (!SQL_SUCCEEDED(sr)) {
666 m_log.error("SQLBindParam failed (context = %s)", context);
667 log_error(stmt, SQL_HANDLE_STMT);
668 throw IOException("ODBC StorageService failed to update record.");
670 m_log.debug("SQLBindParam succeded (context = %s)", context);
674 pair<bool,bool> logres;
676 logres = make_pair(false,false);
680 return 0; // went missing?
681 else if (SQL_SUCCEEDED(sr)) {
682 m_log.debug("SQLExecute of update succeeded");
686 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
687 logres = log_error(stmt, SQL_HANDLE_STMT);
688 } while (attempts && logres.first);
690 throw IOException("ODBC StorageService failed to update record.");
693 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
696 xmltooling::NDC ndc("deleteRow");
699 // Get statement handle.
700 ODBCConn conn(getHDBC());
701 SQLHSTMT stmt = getHSTMT(conn);
703 // Prepare and execute delete statement.
704 char *scontext = makeSafeSQL(context);
705 char *skey = makeSafeSQL(key);
706 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND id='" + skey + "'";
707 freeSafeSQL(scontext, context);
708 freeSafeSQL(skey, key);
709 m_log.debug("SQL: %s", q.c_str());
711 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
714 else if (!SQL_SUCCEEDED(sr)) {
715 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
716 log_error(stmt, SQL_HANDLE_STMT);
717 throw IOException("ODBC StorageService failed to delete record.");
724 void ODBCStorageService::cleanup()
727 xmltooling::NDC ndc("cleanup");
730 Mutex* mutex = Mutex::create();
734 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
737 shutdown_wait->timedwait(mutex, m_cleanupInterval);
743 catch (exception& ex) {
744 m_log.error("cleanup thread swallowed exception: %s", ex.what());
748 m_log.info("cleanup thread exiting...");
755 void* ODBCStorageService::cleanup_fn(void* cache_p)
757 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
760 // First, let's block all signals
761 Thread::mask_all_signals();
764 // Now run the cleanup process.
769 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
772 xmltooling::NDC ndc("updateContext");
775 // Get statement handle.
776 ODBCConn conn(getHDBC());
777 SQLHSTMT stmt = getHSTMT(conn);
780 timestampFromTime(expiration, timebuf);
783 timestampFromTime(time(NULL), nowbuf);
785 char *scontext = makeSafeSQL(context);
787 q = q + table + " SET expires = " + timebuf + " WHERE context='" + scontext + "' AND expires > " + nowbuf;
788 freeSafeSQL(scontext, context);
790 m_log.debug("SQL: %s", q.c_str());
792 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
793 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
794 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
795 log_error(stmt, SQL_HANDLE_STMT);
796 throw IOException("ODBC StorageService failed to update context expiration.");
800 void ODBCStorageService::reap(const char *table, const char* context)
803 xmltooling::NDC ndc("reap");
806 // Get statement handle.
807 ODBCConn conn(getHDBC());
808 SQLHSTMT stmt = getHSTMT(conn);
810 // Prepare and execute delete statement.
812 timestampFromTime(time(NULL), nowbuf);
815 char *scontext = makeSafeSQL(context);
816 q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= " + nowbuf;
817 freeSafeSQL(scontext, context);
820 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
822 m_log.debug("SQL: %s", q.c_str());
824 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
825 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
826 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
827 log_error(stmt, SQL_HANDLE_STMT);
828 throw IOException("ODBC StorageService failed to purge expired records.");
832 void ODBCStorageService::deleteContext(const char *table, const char* context)
835 xmltooling::NDC ndc("deleteContext");
838 // Get statement handle.
839 ODBCConn conn(getHDBC());
840 SQLHSTMT stmt = getHSTMT(conn);
842 // Prepare and execute delete statement.
843 char *scontext = makeSafeSQL(context);
844 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
845 freeSafeSQL(scontext, context);
846 m_log.debug("SQL: %s", q.c_str());
848 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
849 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
850 m_log.error("error deleting context (t=%s, c=%s)", table, context);
851 log_error(stmt, SQL_HANDLE_STMT);
852 throw IOException("ODBC StorageService failed to delete context.");
856 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
858 // Register this SS type
859 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
863 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
865 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");