Initial checkin of ODBC storage service
[shibboleth/sp.git] / odbc-store / odbc-store.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * odbc-store.cpp - Storage service using ODBC
19  *
20  * $Id$
21  */
22
23 // eventually we might be able to support autoconf via cygwin...
24 #if defined (_MSC_VER) || defined(__BORLANDC__)
25 # include "config_win32.h"
26 #else
27 # include "config.h"
28 #endif
29
30 #ifdef WIN32
31 # define _CRT_NONSTDC_NO_DEPRECATE 1
32 # define _CRT_SECURE_NO_DEPRECATE 1
33 # define NOMINMAX
34 # define SHIBODBC_EXPORTS __declspec(dllexport)
35 #else
36 # define SHIBODBC_EXPORTS
37 #endif
38
39 #include <shib-target/shib-target.h>
40 #include <shibsp/exceptions.h>
41 #include <log4cpp/Category.hh>
42 #include <xmltooling/util/NDC.h>
43 #include <xmltooling/util/Threads.h>
44
45 #include <ctime>
46 #include <algorithm>
47 #include <sstream>
48
49 #include <sql.h>
50 #include <sqlext.h>
51
52 #ifdef HAVE_LIBDMALLOCXX
53 #include <dmalloc.h>
54 #endif
55
56 using namespace shibsp;
57 using namespace shibtarget;
58 using namespace opensaml::saml2md;
59 using namespace saml;
60 using namespace xmltooling;
61 using namespace log4cpp;
62 using namespace std;
63
64 #define PLUGIN_VER_MAJOR 3
65 #define PLUGIN_VER_MINOR 0
66
67 #define COLSIZE_KEY 64
68 #define COLSIZE_CONTEXT 256
69 #define COLSIZE_STRING_VALUE 256
70
71
72 /* tables definitions - not used here */
73
74 #define STRING_TABLE "STRING_STORE"
75
76 #define STRING_TABLE \
77   "CREATE TABLE STRING_TABLE ( "\
78   "context VARCHAR( COLSIZE_CONTEXT ), " \
79   "key VARCHAR( COLSIZE_KEY ), " \
80   "value VARCHAR( COLSIZE_STRING_VALUE ), " \
81   "expires TIMESTAMP, "
82   "PRIMARY KEY (context, key), "
83   "INDEX (context))"
84
85
86 #define TEXT_TABLE "TEXT_STORE"
87
88 #define TEXT_TABLE \
89   "CREATE TABLE TEXT_TABLE ( "\
90   "context VARCHAR( COLSIZE_CONTEXT ), " \
91   "key VARCHAR( COLSIZE_KEY ), " \
92   "value LONG TEXT, " \
93   "expires TIMESTAMP, "
94   "PRIMARY KEY (context, key), "
95   "INDEX (context))"
96
97
98
99
100 static const XMLCh ConnectionString[] =
101 { chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t, chLatin_i, chLatin_o, chLatin_n,
102   chLatin_S, chLatin_t, chLatin_r, chLatin_i, chLatin_n, chLatin_g, chNull
103 };
104 static const XMLCh cleanupInterval[] =
105 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
106   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
107 };
108 static const XMLCh cacheTimeout[] =
109 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
110 static const XMLCh odbcTimeout[] =
111 { chLatin_o, chLatin_d, chLatin_b, chLatin_c, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
112 static const XMLCh storeAttributes[] =
113 { chLatin_s, chLatin_t, chLatin_o, chLatin_r, chLatin_e, chLatin_A, chLatin_t, chLatin_t, chLatin_r, chLatin_i, chLatin_b, chLatin_u, chLatin_t, chLatin_e, chLatin_s, chNull };
114
115 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
116
117
118 // ODBC tools
119
120 struct ODBCConn {
121     ODBCConn(SQLHDBC conn) : handle(conn) {}
122     ~ODBCConn() {SQLFreeHandle(SQL_HANDLE_DBC,handle);}
123     operator SQLHDBC() {return handle;}
124     SQLHDBC handle;
125 };
126
127 class ODBCBase : public virtual saml::IPlugIn
128 {
129 public:
130     ODBCBase(const DOMElement* e);
131     virtual ~ODBCBase();
132
133     SQLHDBC getHDBC();
134
135     Category* log;
136
137 protected:
138     const DOMElement* m_root; // can only use this during initialization
139     string m_connstring;
140
141     static SQLHENV m_henv;          // single handle for both plugins
142     bool m_bInitializedODBC;        // tracks which class handled the process
143     static const char* p_connstring;
144
145     pair<int,int> getVersion(SQLHDBC);
146     void log_error(SQLHANDLE handle, SQLSMALLINT htype);
147 };
148
149 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
150 const char* ODBCBase::p_connstring = NULL;
151
152 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
153 {
154 #ifdef _DEBUG
155     xmltooling::NDC ndc("ODBCBase");
156 #endif
157     log = &(Category::getInstance("shibtarget.ODBC"));
158
159     if (m_henv == SQL_NULL_HANDLE) {
160         // Enable connection pooling.
161         SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
162
163         // Allocate the environment.
164         if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
165             throw ConfigurationException("ODBC failed to initialize.");
166
167         // Specify ODBC 3.x
168         SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
169
170         log->info("ODBC initialized");
171         m_bInitializedODBC = true;
172     }
173
174     // Grab connection string from the configuration.
175     e=XMLHelper::getFirstChildElement(e,ConnectionString);
176     if (!e || !e->hasChildNodes()) {
177         if (!p_connstring) {
178             this->~ODBCBase();
179             throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
180         }
181         m_connstring=p_connstring;
182     }
183     else {
184         xmltooling::auto_ptr_char arg(e->getFirstChild()->getNodeValue());
185         m_connstring=arg.get();
186         p_connstring=m_connstring.c_str();
187     }
188
189     // Connect and check version.
190     SQLHDBC conn=getHDBC();
191     pair<int,int> v=getVersion(conn);
192     SQLFreeHandle(SQL_HANDLE_DBC,conn);
193
194     // Make sure we've got the right version.
195     if (v.first != PLUGIN_VER_MAJOR) {
196         this->~ODBCBase();
197         log->crit("unknown database version: %d.%d", v.first, v.second);
198         throw SAMLException("Unknown cache database version.");
199     }
200 }
201
202 ODBCBase::~ODBCBase()
203 {
204     //delete m_mysql;
205     if (m_bInitializedODBC)
206         SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
207     m_bInitializedODBC=false;
208     m_henv = SQL_NULL_HANDLE;
209     p_connstring=NULL;
210 }
211
212 void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
213 {
214     SQLSMALLINT  i = 0;
215     SQLINTEGER   native;
216     SQLCHAR      state[7];
217     SQLCHAR      text[256];
218     SQLSMALLINT  len;
219     SQLRETURN    ret;
220
221     do {
222         ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
223         if (SQL_SUCCEEDED(ret))
224             log->error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
225     } while(SQL_SUCCEEDED(ret));
226 }
227
228 SQLHDBC ODBCBase::getHDBC()
229 {
230 #ifdef _DEBUG
231     xmltooling::NDC ndc("getMYSQL");
232 #endif
233
234     // Get a handle.
235     SQLHDBC handle;
236     SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
237     if (!SQL_SUCCEEDED(sr)) {
238         log->error("failed to allocate connection handle");
239         log_error(m_henv, SQL_HANDLE_ENV);
240         throw SAMLException("ODBCBase::getHDBC failed to allocate connection handle");
241     }
242
243     sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
244     if (!SQL_SUCCEEDED(sr)) {
245         log->error("failed to connect to database");
246         log_error(handle, SQL_HANDLE_DBC);
247         throw SAMLException("ODBCBase::getHDBC failed to connect to database");
248     }
249
250     return handle;
251 }
252
253 pair<int,int> ODBCBase::getVersion(SQLHDBC conn)
254 {
255     // Grab the version number from the database.
256     SQLHSTMT hstmt;
257     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
258     
259     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
260     if (!SQL_SUCCEEDED(sr)) {
261         log->error("failed to read version from database");
262         log_error(hstmt, SQL_HANDLE_STMT);
263         throw SAMLException("ODBCBase::getVersion failed to read version from database");
264     }
265
266     SQLINTEGER major;
267     SQLINTEGER minor;
268     SQLBindCol(hstmt,1,SQL_C_SLONG,&major,0,NULL);
269     SQLBindCol(hstmt,2,SQL_C_SLONG,&minor,0,NULL);
270
271     if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
272         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
273         return pair<int,int>(major,minor);
274     }
275
276     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
277     log->error("no rows returned in version query");
278     throw SAMLException("ODBCBase::getVersion failed to read version from database");
279 }
280
281
282 // ------------------------------------------------------------
283
284 // ODBC Storage Service class
285
286 class ODBCStorageService : public ODBCBase, public StorageService
287 {
288     string stringTable = STRING_TABLE;
289     string textTable = TEXT_TABLE;
290
291 public:
292     ODBCStorageService(const DOMElement* e);
293     virtual ~ODBCStorageService();
294
295     void createString(const char* context, const char* key, const char* value, time_t expiration) {
296         return createRow(string_table, context, key, value, expiration);
297     }
298     bool readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL) {
299         return readRow(string_table, context, key, value, expiration, COLSIZE_STRING_VALUE);
300     }
301     bool updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {
302         return updateRow(string_table, context, key, value, expiration);
303     }
304     bool deleteString(const char* context, const char* key) {
305         return deleteRow(string_table, context, key, value, expiration);
306     }
307
308     void createText(const char* context, const char* key, const char* value, time_t expiration) {
309         return createRow(text_table, context, key, value, expiration);
310     }
311     bool readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL) {
312         return readRow(text_table, context, key, value, expiration, 0);
313     }
314     bool updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {
315         return updateRow(text_table, context, key, value, expiration);
316     }
317     bool deleteText(const char* context, const char* key) {
318         return deleteRow(text_table, context, key, value, expiration);
319     }
320
321     void reap(const char* context) {
322         reap(string_table, context);
323         reap(text_table, context);
324     }
325     void deleteContext(const char* context) {
326         deleteCtx(string_table, context);
327         deleteCtx(text_table, context);
328     }
329      
330
331 private:
332
333     void createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
334     bool readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int maxsize);
335     bool updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
336     bool deleteRow(const char *table, const char* context, const char* key);
337
338     void reapRows(const char* table, const char* context);
339     void deleteCtx(const char* table, const char* context);
340
341     xmltooling::CondWait* shutdown_wait;
342     bool shutdown;
343     xmltooling::Thread* cleanup_thread;
344
345     static void* cleanup_fcn(void*); 
346     void cleanup();
347
348     CondWait* shutdown_wait;
349     Thread* cleanup_thread;
350     bool shutdown;
351     int m_cleanupInterval;
352     Category& log;
353
354     StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
355     {
356         return new ODBCStorageService(e);
357     }
358
359     // convert SQL timestamp to time_t 
360     time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
361     {
362         time_t ret;
363         struct tm t;
364         t.tm_sec=expires.second;
365         t.tm_min=expires.minute;
366         t.tm_hour=expires.hour;
367         t.tm_mday=expires.day;
368         t.tm_mon=expires.month-1;
369         t.tm_year=expires.year-1900;
370         t.tm_isdst=0;
371 #if defined(HAVE_TIMEGM)
372         ret = timegm(&t);
373 #else
374         ret = mktime(&t) - timezone;
375 #endif
376         return (ret);
377     }
378
379     // conver time_t to SQL string
380     void timestampFromTime(time_t t, char &ret)
381     {
382 #ifdef HAVE_GMTIME_R
383         struct tm res;
384         struct tm* ptime=gmtime_r(&created,&res);
385 #else
386         struct tm* ptime=gmtime(&created);
387 #endif
388         strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
389     }
390
391 };
392
393 // class constructor
394
395 ODBCStorageService::ODBCStorageService(const DOMElement* e):
396    ODBCBase(e),
397    shutdown(false),
398    m_cleanupInterval(0)
399
400 {
401 #ifdef _DEBUG
402     xmltooling::NDC ndc("ODBCStorageService");
403 #endif
404     log = &(Category::getInstance("shibtarget.StorageService.ODBC"));
405
406     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
407     if (tag && *tag) {
408         m_cleanupInterval = XMLString::parseInt(tag);
409     }
410     if (!m_cleanupInterval) m_cleanupInterval=300;
411
412     contextLock = Mutex::create();
413     shutdown_wait = CondWait::create();
414
415     // Initialize the cleanup thread
416     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
417 }
418
419 ODBCStorageService::~ODBCStorageService()
420 {
421     shutdown = true;
422     shutdown_wait->signal();
423     cleanup_thread->join(NULL);
424
425     delete shutdown_wait;
426 }
427
428 // create 
429
430 HRESULT ODBCStorageService::createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration)
431 {
432 #ifdef _DEBUG
433     xmltooling::NDC ndc("createRow");
434 #endif
435
436     char timebuf[32];
437     timestampFromTime(expiration, timebuf);
438
439     // Get statement handle.
440     SQLHSTMT hstmt;
441     ODBCConn conn(getHDBC());
442     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
443
444     // Prepare and exectute insert statement.
445     string q  = string("INSERT ") + table + " VALUES ('" + context + "','" + key + "','" + value + "'," + timebuf + "')";
446     log->debug("SQL: %s", q.str());
447
448     HRESULT hr=NOERROR;
449     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
450     if (!SQL_SUCCEEDED(sr)) {
451         log->error("insert record failed (t=%s, c=%s, k=%s", table, context, key);
452         log_error(hstmt, SQL_HANDLE_STMT);
453         hr=E_FAIL;
454     }
455
456     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
457     return hr;
458 }
459
460 // read
461
462 HRESULT ODBCStorageService::readRow(const char *table, const char* context, const char* key, string& pvalue, time_t& pexpiration, int maxsize)
463 {
464 #ifdef _DEBUG
465     xmltooling::NDC ndc("readRow");
466 #endif
467
468     SQLCHAR *tvalue = NULL;
469     SQL_TIMESTAMP_STRUCT expires;
470     time_t exp;
471
472     // Get statement handle.
473     SQLHSTMT hstmt;
474     ODBCConn conn(getHDBC());
475     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
476
477     // Prepare and exectute select statement.
478     string q = string("SELECT expires,value FROM ") + table +
479                " WHERE context='" + context + "' AND key='" + key + "'";
480     log->debug("SQL: %s", q.str());
481
482     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
483     if (!SQL_SUCCEEDED(sr)) {
484         log->error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
485         log_error(hstmt, SQL_HANDLE_STMT);
486         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
487         return E_FAIL;
488     }
489
490     // retrieve data 
491     SQLBindCol(hstmt,1,SQL_C_TYPE_TIMESTAMP,&expires,0,NULL);
492     // SQLBindCol(hstmt,1,SQL_C_CHAR,value,sizeof(value),NULL);
493
494     if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
495         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
496         return S_FALSE;
497     }
498
499     // expire time from bound col
500     exp = timeFromTimestamp(expires);
501     if (time(NULL)>ezp) {
502         log->debug(".. expired");
503         return false;
504     }
505     if (pexpiration) pexpiration = exp;
506
507     // value by getdata
508
509     // see how much text there is
510     if (maxsize==0) {
511          SQLINTEGER nch;
512          SQLCHAR tv[12];
513          sr = SQLGetData(hstmt, 2, SQL_C_CHAR, tvp, BUFSIZE_TEXT_BLOCK, &nch);
514          if (sr==SQL_SUCCESS || sr==SQL_SUCCESS_WITH_INFO) {
515              maxsize = nch;
516          }
517     }
518
519     tvalue = (SQLCHAR*) malloc(maxsize+1);
520     sr = SQLGetData(hstmt, 2, SQL_C_CHAR, tvalue, maxsize, &nch);
521         if (sr!=SQL_SUCCESS) {
522             log->error("error retriving value for (t=%s, c=%s, k=%s)", table, context, key);
523             log_error(hstmt, SQL_HANDLE_STMT);
524             SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
525             return E_FAIL;
526         }
527     }
528     pvalue = string(tvalue);
529     free(tvalue);
530
531     log->debug(".. value found");
532
533     return sr;
534 }
535
536
537 // update 
538
539 HRESULT ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration)
540 {
541 #ifdef _DEBUG
542     xmltooling::NDC ndc("updateRow");
543 #endif
544
545     char timebuf[32];
546     timestampFromTime(expiration, timebuf);
547
548     // Get statement handle.
549     SQLHSTMT hstmt;
550     ODBCConn conn(getHDBC());
551     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
552
553     // Prepare and exectute update statement.
554
555     string expstr = "";
556     if (expiration) expstr = string(",expires = '") + timebuf + "' ";
557
558     string q  = string("UPDATE ") + table + " SET value='" + value + "'" + expstr + 
559                " WHERE context='" + context + "' AND key='" + key + "' AND expires > NOW()";
560     log->debug("SQL: %s", q.str());
561
562     HRESULT hr=NOERROR;
563     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
564     if (!SQL_SUCCEEDED(sr)) {
565         log->error("update record failed (t=%s, c=%s, k=%s", table, context, key);
566         log_error(hstmt, SQL_HANDLE_STMT);
567         hr=E_FAIL;
568     }
569
570     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
571     return hr;
572 }
573
574
575 // delete
576
577 HRESULT ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
578 {
579 #ifdef _DEBUG
580     xmltooling::NDC ndc("deleteRow");
581 #endif
582
583     // Get statement handle.
584     SQLHSTMT hstmt;
585     ODBCConn conn(getHDBC());
586     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
587
588     // Prepare and execute delete statement.
589     string q = string("DELETE FROM ") + table + " WHERE context='" + context + "' AND key='" + key + "'";
590     log->debug("SQL: %s", q.str());
591
592     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
593  
594     HRESULT hr=NOERROR;
595     if (sr==SQL_NO_DATA)
596         hr=S_FALSE;
597     else if (!SQL_SUCCEEDED(sr)) {
598         log->error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
599         log_error(hstmt, SQL_HANDLE_STMT);
600         hr=E_FAIL;
601     }
602
603     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
604     return hr;
605 }
606
607
608 // cleanup - delete expired entries
609
610 void ODBCStorageService::cleanup()
611 {
612 #ifdef _DEBUG
613     xmltooling::NDC ndc("cleanup");
614 #endif
615
616     Mutex* mutex = xmltooling::Mutex::create();
617
618     int rerun_timer = 0;
619     int timeout_life = 0;
620
621     mutex->lock();
622
623     log->info("cleanup thread started... running every %d secs", m_cleanupInterval);
624
625     while (!shutdown) {
626         shutdown_wait->timedwait(mutex, m_cleanupInterval);
627
628         if (shutdown) break;
629
630         reap(NULL);
631     }
632
633     log->info("cleanup thread exiting...");
634
635     mutex->unlock();
636     delete mutex;
637     xmltooling::Thread::exit(NULL);
638 }
639
640 void* ODBCStorageService::cleanup_fcn(void* cache_p)
641 {
642   ODBCStorageService* cache = (ODBCStorageService*)cache_p;
643
644 #ifndef WIN32
645   // First, let's block all signals
646   Thread::mask_all_signals();
647 #endif
648
649   // Now run the cleanup process.
650   cache->cleanup();
651   return NULL;
652 }
653
654
655 // remove expired entries for a context
656
657 void ODBCStorageService::reapRows(const char *table, const char* context)
658 {
659 #ifdef _DEBUG
660     xmltooling::NDC ndc("reapRows");
661 #endif
662
663     // Get statement handle.
664     SQLHSTMT hstmt;
665     ODBCConn conn(getHDBC());
666     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
667
668     // Prepare and execute delete statement.
669     string q;
670     if (context) {
671         q = string("DELETE FROM ") + table + " WHERE context='" + context + "' AND expires<NOW()";
672     } else {
673         q = string("DELETE FROM ") + table + " WHERE expires<NOW()";
674     }
675     log->debug("SQL: %s", q.str());
676
677     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
678  
679     HRESULT hr=NOERROR;
680     if (sr==SQL_NO_DATA)
681         hr=S_FALSE;
682     else if (!SQL_SUCCEEDED(sr)) {
683         log->error("error expiring records (t=%s, c=%s)", table, context?context:"null");
684         log_error(hstmt, SQL_HANDLE_STMT);
685         hr=E_FAIL;
686     }
687
688     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
689 }
690
691
692
693 // remove all entries for a context
694
695 void ODBCStorageService::deleteCtx(const char *table, const char* context)
696 {
697 #ifdef _DEBUG
698     xmltooling::NDC ndc("deleteCtx");
699 #endif
700
701     // Get statement handle.
702     SQLHSTMT hstmt;
703     ODBCConn conn(getHDBC());
704     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
705
706     // Prepare and execute delete statement.
707     string q = string("DELETE FROM ") + table + " WHERE context='" + context + "'";
708     log->debug("SQL: %s", q.str());
709
710     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
711  
712     HRESULT hr=NOERROR;
713     if (sr==SQL_NO_DATA)
714         hr=S_FALSE;
715     else if (!SQL_SUCCEEDED(sr)) {
716         log->error("error deleting context (t=%s, c=%s)", table, context);
717         log_error(hstmt, SQL_HANDLE_STMT);
718         hr=E_FAIL;
719     }
720
721     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
722 }