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 ConnectionString[] = UNICODE_LITERAL_16(C,o,n,n,e,c,t,i,o,n,S,t,r,i,n,g);
97 // RAII for ODBC handles
99 ODBCConn(SQLHDBC conn) : handle(conn) {}
101 SQLRETURN sr = SQLEndTran(SQL_HANDLE_DBC, handle, SQL_COMMIT);
102 SQLDisconnect(handle);
103 SQLFreeHandle(SQL_HANDLE_DBC,handle);
104 if (!SQL_SUCCEEDED(sr))
105 throw IOException("Failed to commit connection.");
107 operator SQLHDBC() {return handle;}
111 class ODBCStorageService : public StorageService
114 ODBCStorageService(const DOMElement* e);
115 virtual ~ODBCStorageService();
117 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
118 return createRow(STRING_TABLE, context, key, value, expiration);
120 int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
121 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
123 int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
124 return updateRow(STRING_TABLE, context, key, value, expiration, version);
126 bool deleteString(const char* context, const char* key) {
127 return deleteRow(STRING_TABLE, context, key);
130 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
131 return createRow(TEXT_TABLE, context, key, value, expiration);
133 int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
134 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
136 int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
137 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
139 bool deleteText(const char* context, const char* key) {
140 return deleteRow(TEXT_TABLE, context, key);
143 void reap(const char* context) {
144 reap(STRING_TABLE, context);
145 reap(TEXT_TABLE, context);
148 void updateContext(const char* context, time_t expiration) {
149 updateContext(STRING_TABLE, context, expiration);
150 updateContext(TEXT_TABLE, context, expiration);
153 void deleteContext(const char* context) {
154 deleteContext(STRING_TABLE, context);
155 deleteContext(TEXT_TABLE, context);
160 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
161 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
162 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
163 bool deleteRow(const char *table, const char* context, const char* key);
165 void reap(const char* table, const char* context);
166 void updateContext(const char* table, const char* context, time_t expiration);
167 void deleteContext(const char* table, const char* context);
170 SQLHSTMT getHSTMT(SQLHDBC);
171 pair<int,int> getVersion(SQLHDBC);
172 bool log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=NULL);
174 static void* cleanup_fn(void*);
178 int m_cleanupInterval;
179 CondWait* shutdown_wait;
180 Thread* cleanup_thread;
187 StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
189 return new ODBCStorageService(e);
192 // convert SQL timestamp to time_t
193 time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
197 t.tm_sec=expires.second;
198 t.tm_min=expires.minute;
199 t.tm_hour=expires.hour;
200 t.tm_mday=expires.day;
201 t.tm_mon=expires.month-1;
202 t.tm_year=expires.year-1900;
204 #if defined(HAVE_TIMEGM)
207 ret = mktime(&t) - timezone;
212 // conver time_t to SQL string
213 void timestampFromTime(time_t t, char* ret)
217 struct tm* ptime=gmtime_r(&t,&res);
219 struct tm* ptime=gmtime(&t);
221 strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
224 // make a string safe for SQL command
225 // result to be free'd only if it isn't the input
226 static char *makeSafeSQL(const char *src)
232 // see if any conversion needed
233 for (s=(char*)src; *s; nc++,s++) if (*s=='\'') ns++;
234 if (ns==0) return ((char*)src);
236 char *safe = new char[(nc+2*ns+1)];
237 for (s=safe; *src; src++) {
238 if (*src=='\'') *s++ = '\'';
245 void freeSafeSQL(char *safe, const char *src)
252 ODBCStorageService::ODBCStorageService(const DOMElement* e) : m_log(Category::getInstance("XMLTooling.StorageService")),
253 m_cleanupInterval(900), shutdown_wait(NULL), cleanup_thread(NULL), shutdown(false), m_henv(SQL_NULL_HANDLE)
256 xmltooling::NDC ndc("ODBCStorageService");
259 const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
261 m_cleanupInterval = XMLString::parseInt(tag);
262 if (!m_cleanupInterval)
263 m_cleanupInterval = 900;
265 if (m_henv == SQL_NULL_HANDLE) {
266 // Enable connection pooling.
267 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
269 // Allocate the environment.
270 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
271 throw XMLToolingException("ODBC failed to initialize.");
274 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
276 m_log.info("ODBC initialized");
279 // Grab connection string from the configuration.
280 e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : NULL;
281 if (!e || !e->hasChildNodes()) {
282 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
283 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
285 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
286 m_connstring=arg.get();
288 // Connect and check version.
289 ODBCConn conn(getHDBC());
290 pair<int,int> v=getVersion(conn);
292 // Make sure we've got the right version.
293 if (v.first != PLUGIN_VER_MAJOR) {
294 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
295 m_log.crit("unknown database version: %d.%d", v.first, v.second);
296 throw XMLToolingException("Unknown database version for ODBC StorageService.");
299 // Initialize the cleanup thread
300 shutdown_wait = CondWait::create();
301 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
304 ODBCStorageService::~ODBCStorageService()
307 shutdown_wait->signal();
308 cleanup_thread->join(NULL);
309 delete shutdown_wait;
310 if (m_henv != SQL_NULL_HANDLE)
311 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
314 bool ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
325 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
326 if (SQL_SUCCEEDED(ret)) {
327 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
328 if (checkfor && !strcmp(checkfor, (const char*)state))
331 } while(SQL_SUCCEEDED(ret));
335 SQLHDBC ODBCStorageService::getHDBC()
338 xmltooling::NDC ndc("getHDBC");
343 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
344 if (!SQL_SUCCEEDED(sr)) {
345 m_log.error("failed to allocate connection handle");
346 log_error(m_henv, SQL_HANDLE_ENV);
347 throw IOException("ODBC StorageService failed to allocate a connection handle.");
350 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
351 if (!SQL_SUCCEEDED(sr)) {
352 m_log.error("failed to connect to database");
353 log_error(handle, SQL_HANDLE_DBC);
354 throw IOException("ODBC StorageService failed to connect to database.");
357 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, NULL);
358 if (!SQL_SUCCEEDED(sr))
359 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
360 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_SERIALIZABLE, NULL);
361 if (!SQL_SUCCEEDED(sr))
362 throw IOException("ODBC StorageService failed to enable transaction isolation.");
367 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
370 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
371 if (!SQL_SUCCEEDED(sr)) {
372 m_log.error("failed to allocate statement handle");
373 log_error(conn, SQL_HANDLE_DBC);
374 throw IOException("ODBC StorageService failed to allocate a statement handle.");
379 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
381 // Grab the version number from the database.
382 SQLHSTMT stmt = getHSTMT(conn);
384 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
385 if (!SQL_SUCCEEDED(sr)) {
386 m_log.error("failed to read version from database");
387 log_error(stmt, SQL_HANDLE_STMT);
388 throw IOException("ODBC StorageService failed to read version from database.");
393 SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,NULL);
394 SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,NULL);
396 if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
397 return pair<int,int>(major,minor);
399 m_log.error("no rows returned in version query");
400 throw IOException("ODBC StorageService failed to read version from database.");
403 bool ODBCStorageService::createRow(const char* table, const char* context, const char* key, const char* value, time_t expiration)
406 xmltooling::NDC ndc("createRow");
410 timestampFromTime(expiration, timebuf);
412 // Get statement handle.
413 ODBCConn conn(getHDBC());
414 SQLHSTMT stmt = getHSTMT(conn);
416 // Prepare and exectute insert statement.
417 //char *scontext = makeSafeSQL(context);
418 //char *skey = makeSafeSQL(key);
419 //char *svalue = makeSafeSQL(value);
420 string q = string("INSERT INTO ") + table + " VALUES (?,?," + timebuf + ",1,?)";
422 SQLRETURN sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
423 if (!SQL_SUCCEEDED(sr)) {
424 m_log.error("SQLPrepare failed (t=%s, c=%s, k=%s)", table, context, key);
425 log_error(stmt, SQL_HANDLE_STMT);
426 throw IOException("ODBC StorageService failed to insert record.");
428 m_log.debug("SQLPrepare() succeded. SQL: %s", q.c_str());
430 SQLINTEGER b_ind = SQL_NTS;
431 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, SQL_VARCHAR, 0, 0, const_cast<char*>(context), &b_ind);
432 if (!SQL_SUCCEEDED(sr)) {
433 m_log.error("SQLBindParam failed (context = %s)", context);
434 log_error(stmt, SQL_HANDLE_STMT);
435 throw IOException("ODBC StorageService failed to insert record.");
437 m_log.debug("SQLBindParam succeded (context = %s)", context);
439 sr = SQLBindParam(stmt, 2, SQL_C_CHAR, SQL_VARCHAR, 0, 0, const_cast<char*>(key), &b_ind);
440 if (!SQL_SUCCEEDED(sr)) {
441 m_log.error("SQLBindParam failed (key = %s)", key);
442 log_error(stmt, SQL_HANDLE_STMT);
443 throw IOException("ODBC StorageService failed to insert record.");
445 m_log.debug("SQLBindParam succeded (key = %s)", key);
447 sr = SQLBindParam(stmt, 3, SQL_C_CHAR, (strcmp(table, TEXT_TABLE)==0 ? SQL_LONGVARCHAR : SQL_VARCHAR), 0, 0, const_cast<char*>(value), &b_ind);
448 if (!SQL_SUCCEEDED(sr)) {
449 m_log.error("SQLBindParam failed (value = %s)", value);
450 log_error(stmt, SQL_HANDLE_STMT);
451 throw IOException("ODBC StorageService failed to insert record.");
453 m_log.debug("SQLBindParam succeded (value = %s)", value);
455 //freeSafeSQL(scontext, context);
456 //freeSafeSQL(skey, key);
457 //freeSafeSQL(svalue, value);
458 //m_log.debug("SQL: %s", q.c_str());
461 if (!SQL_SUCCEEDED(sr)) {
462 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
463 if (log_error(stmt, SQL_HANDLE_STMT, "23000"))
464 return false; // supposedly integrity violation?
465 throw IOException("ODBC StorageService failed to insert record.");
470 int ODBCStorageService::readRow(
471 const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
475 xmltooling::NDC ndc("readRow");
478 // Get statement handle.
479 ODBCConn conn(getHDBC());
480 SQLHSTMT stmt = getHSTMT(conn);
482 // Prepare and exectute select statement.
484 timestampFromTime(time(NULL), timebuf);
485 char *scontext = makeSafeSQL(context);
486 char *skey = makeSafeSQL(key);
488 q << "SELECT version";
492 q << ",CASE version WHEN " << version << " THEN NULL ELSE value END";
493 q << " FROM " << table << " WHERE context='" << scontext << "' AND id='" << skey << "' AND expires > " << timebuf;
494 freeSafeSQL(scontext, context);
495 freeSafeSQL(skey, key);
496 if (m_log.isDebugEnabled())
497 m_log.debug("SQL: %s", q.str().c_str());
499 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
500 if (!SQL_SUCCEEDED(sr)) {
501 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
502 log_error(stmt, SQL_HANDLE_STMT);
503 throw IOException("ODBC StorageService search failed.");
507 SQL_TIMESTAMP_STRUCT expiration;
509 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
511 SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,NULL);
513 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
517 *pexpiration = timeFromTimestamp(expiration);
520 return version; // nothing's changed, so just echo back the version
524 SQLCHAR buf[LONGDATA_BUFLEN];
525 while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
526 if (!SQL_SUCCEEDED(sr)) {
527 m_log.error("error while reading text field from result set");
528 log_error(stmt, SQL_HANDLE_STMT);
529 throw IOException("ODBC StorageService search failed to read data from result set.");
531 pvalue->append((char*)buf);
538 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
541 xmltooling::NDC ndc("updateRow");
544 if (!value && !expiration)
545 throw IOException("ODBC StorageService given invalid update instructions.");
547 // Get statement handle.
548 ODBCConn conn(getHDBC());
549 SQLHSTMT stmt = getHSTMT(conn);
551 // First, fetch the current version for later, which also ensures the record still exists.
553 timestampFromTime(time(NULL), timebuf);
554 char *scontext = makeSafeSQL(context);
555 char *skey = makeSafeSQL(key);
556 string q("SELECT version FROM ");
557 q = q + table + " WHERE context='" + scontext + "' AND id='" + key + "' AND expires > " + timebuf;
559 m_log.debug("SQL: %s", q.c_str());
561 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
562 if (!SQL_SUCCEEDED(sr)) {
563 freeSafeSQL(scontext, context);
564 freeSafeSQL(skey, key);
565 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
566 log_error(stmt, SQL_HANDLE_STMT);
567 throw IOException("ODBC StorageService search failed.");
571 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
572 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
573 freeSafeSQL(scontext, context);
574 freeSafeSQL(skey, key);
579 if (version > 0 && version != ver) {
580 freeSafeSQL(scontext, context);
581 freeSafeSQL(skey, key);
585 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
586 stmt = getHSTMT(conn);
588 // Prepare and exectute update statement.
589 q = string("UPDATE ") + table + " SET ";
592 q = q + "value=?, version=version+1";
595 timestampFromTime(expiration, timebuf);
598 q = q + "expires = " + timebuf;
601 q = q + " WHERE context='" + scontext + "' AND id='" + key + "'";
602 freeSafeSQL(scontext, context);
603 freeSafeSQL(skey, key);
605 sr = SQLPrepare(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
606 if (!SQL_SUCCEEDED(sr)) {
607 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
608 log_error(stmt, SQL_HANDLE_STMT);
609 throw IOException("ODBC StorageService failed to update record.");
611 m_log.debug("SQLPrepare() succeded. SQL: %s", q.c_str());
613 SQLINTEGER b_ind = SQL_NTS;
615 sr = SQLBindParam(stmt, 1, SQL_C_CHAR, (strcmp(table, TEXT_TABLE)==0 ? SQL_LONGVARCHAR : SQL_VARCHAR), 0, 0, const_cast<char*>(value), &b_ind);
616 if (!SQL_SUCCEEDED(sr)) {
617 m_log.error("SQLBindParam failed (context = %s)", context);
618 log_error(stmt, SQL_HANDLE_STMT);
619 throw IOException("ODBC StorageService failed to update record.");
621 m_log.debug("SQLBindParam succeded (context = %s)", context);
626 return 0; // went missing?
627 else if (!SQL_SUCCEEDED(sr)) {
628 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
629 log_error(stmt, SQL_HANDLE_STMT);
630 throw IOException("ODBC StorageService failed to update record.");
636 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
639 xmltooling::NDC ndc("deleteRow");
642 // Get statement handle.
643 ODBCConn conn(getHDBC());
644 SQLHSTMT stmt = getHSTMT(conn);
646 // Prepare and execute delete statement.
647 char *scontext = makeSafeSQL(context);
648 char *skey = makeSafeSQL(key);
649 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND id='" + skey + "'";
650 freeSafeSQL(scontext, context);
651 freeSafeSQL(skey, key);
652 m_log.debug("SQL: %s", q.c_str());
654 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
657 else if (!SQL_SUCCEEDED(sr)) {
658 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
659 log_error(stmt, SQL_HANDLE_STMT);
660 throw IOException("ODBC StorageService failed to delete record.");
667 void ODBCStorageService::cleanup()
670 xmltooling::NDC ndc("cleanup");
673 Mutex* mutex = Mutex::create();
677 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
680 shutdown_wait->timedwait(mutex, m_cleanupInterval);
686 catch (exception& ex) {
687 m_log.error("cleanup thread swallowed exception: %s", ex.what());
691 m_log.info("cleanup thread exiting...");
698 void* ODBCStorageService::cleanup_fn(void* cache_p)
700 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
703 // First, let's block all signals
704 Thread::mask_all_signals();
707 // Now run the cleanup process.
712 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
715 xmltooling::NDC ndc("updateContext");
718 // Get statement handle.
719 ODBCConn conn(getHDBC());
720 SQLHSTMT stmt = getHSTMT(conn);
723 timestampFromTime(expiration, timebuf);
726 timestampFromTime(time(NULL), nowbuf);
728 char *scontext = makeSafeSQL(context);
730 q = q + table + " SET expires = " + timebuf + " WHERE context='" + scontext + "' AND expires > " + nowbuf;
731 freeSafeSQL(scontext, context);
733 m_log.debug("SQL: %s", q.c_str());
735 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
736 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
737 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
738 log_error(stmt, SQL_HANDLE_STMT);
739 throw IOException("ODBC StorageService failed to update context expiration.");
743 void ODBCStorageService::reap(const char *table, const char* context)
746 xmltooling::NDC ndc("reap");
749 // Get statement handle.
750 ODBCConn conn(getHDBC());
751 SQLHSTMT stmt = getHSTMT(conn);
753 // Prepare and execute delete statement.
755 timestampFromTime(time(NULL), nowbuf);
758 char *scontext = makeSafeSQL(context);
759 q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= " + nowbuf;
760 freeSafeSQL(scontext, context);
763 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
765 m_log.debug("SQL: %s", q.c_str());
767 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
768 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
769 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
770 log_error(stmt, SQL_HANDLE_STMT);
771 throw IOException("ODBC StorageService failed to purge expired records.");
775 void ODBCStorageService::deleteContext(const char *table, const char* context)
778 xmltooling::NDC ndc("deleteContext");
781 // Get statement handle.
782 ODBCConn conn(getHDBC());
783 SQLHSTMT stmt = getHSTMT(conn);
785 // Prepare and execute delete statement.
786 char *scontext = makeSafeSQL(context);
787 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
788 freeSafeSQL(scontext, context);
789 m_log.debug("SQL: %s", q.c_str());
791 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
792 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
793 m_log.error("error deleting context (t=%s, c=%s)", table, context);
794 log_error(stmt, SQL_HANDLE_STMT);
795 throw IOException("ODBC StorageService failed to delete context.");
799 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
801 // Register this SS type
802 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
806 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
808 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");