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 struct ODBCStatement {
112 ODBCStatement(SQLHSTMT statement) : handle(statement) {}
113 ~ODBCStatement() {SQLFreeHandle(SQL_HANDLE_STMT,handle);}
114 operator SQLHSTMT() {return handle;}
118 class ODBCStorageService : public StorageService
121 ODBCStorageService(const DOMElement* e);
122 virtual ~ODBCStorageService();
124 bool createString(const char* context, const char* key, const char* value, time_t expiration) {
125 return createRow(STRING_TABLE, context, key, value, expiration);
127 int readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
128 return readRow(STRING_TABLE, context, key, pvalue, pexpiration, version, false);
130 int updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
131 return updateRow(STRING_TABLE, context, key, value, expiration, version);
133 bool deleteString(const char* context, const char* key) {
134 return deleteRow(STRING_TABLE, context, key);
137 bool createText(const char* context, const char* key, const char* value, time_t expiration) {
138 return createRow(TEXT_TABLE, context, key, value, expiration);
140 int readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL, int version=0) {
141 return readRow(TEXT_TABLE, context, key, pvalue, pexpiration, version, true);
143 int updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0, int version=0) {
144 return updateRow(TEXT_TABLE, context, key, value, expiration, version);
146 bool deleteText(const char* context, const char* key) {
147 return deleteRow(TEXT_TABLE, context, key);
150 void reap(const char* context) {
151 reap(STRING_TABLE, context);
152 reap(TEXT_TABLE, context);
155 void updateContext(const char* context, time_t expiration) {
156 updateContext(STRING_TABLE, context, expiration);
157 updateContext(TEXT_TABLE, context, expiration);
160 void deleteContext(const char* context) {
161 deleteContext(STRING_TABLE, context);
162 deleteContext(TEXT_TABLE, context);
167 bool createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
168 int readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text);
169 int updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version);
170 bool deleteRow(const char *table, const char* context, const char* key);
172 void reap(const char* table, const char* context);
173 void updateContext(const char* table, const char* context, time_t expiration);
174 void deleteContext(const char* table, const char* context);
177 SQLHSTMT getHSTMT(SQLHDBC);
178 pair<int,int> getVersion(SQLHDBC);
179 bool log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor=NULL);
181 static void* cleanup_fn(void*);
185 int m_cleanupInterval;
186 CondWait* shutdown_wait;
187 Thread* cleanup_thread;
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)
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 if (m_henv == SQL_NULL_HANDLE) {
273 // Enable connection pooling.
274 SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
276 // Allocate the environment.
277 if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
278 throw XMLToolingException("ODBC failed to initialize.");
281 SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
283 m_log.info("ODBC initialized");
286 // Grab connection string from the configuration.
287 e = e ? XMLHelper::getFirstChildElement(e,ConnectionString) : NULL;
288 if (!e || !e->hasChildNodes()) {
289 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
290 throw XMLToolingException("ODBC StorageService requires ConnectionString element in configuration.");
292 auto_ptr_char arg(e->getFirstChild()->getNodeValue());
293 m_connstring=arg.get();
295 // Connect and check version.
296 ODBCConn conn(getHDBC());
297 pair<int,int> v=getVersion(conn);
299 // Make sure we've got the right version.
300 if (v.first != PLUGIN_VER_MAJOR) {
301 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
302 m_log.crit("unknown database version: %d.%d", v.first, v.second);
303 throw XMLToolingException("Unknown database version for ODBC StorageService.");
306 // Initialize the cleanup thread
307 shutdown_wait = CondWait::create();
308 cleanup_thread = Thread::create(&cleanup_fn, (void*)this);
311 ODBCStorageService::~ODBCStorageService()
314 shutdown_wait->signal();
315 cleanup_thread->join(NULL);
316 delete shutdown_wait;
317 SQLFreeHandle(SQL_HANDLE_ENV, m_henv);
320 bool ODBCStorageService::log_error(SQLHANDLE handle, SQLSMALLINT htype, const char* checkfor)
331 ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
332 if (SQL_SUCCEEDED(ret)) {
333 m_log.error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
334 if (checkfor && !strcmp(checkfor, (const char*)state))
337 } while(SQL_SUCCEEDED(ret));
341 SQLHDBC ODBCStorageService::getHDBC()
344 xmltooling::NDC ndc("getHDBC");
349 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
350 if (!SQL_SUCCEEDED(sr)) {
351 m_log.error("failed to allocate connection handle");
352 log_error(m_henv, SQL_HANDLE_ENV);
353 throw IOException("ODBC StorageService failed to allocate a connection handle.");
356 sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
357 if (!SQL_SUCCEEDED(sr)) {
358 m_log.error("failed to connect to database");
359 log_error(handle, SQL_HANDLE_DBC);
360 throw IOException("ODBC StorageService failed to connect to database.");
363 sr = SQLSetConnectAttr(handle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, NULL);
364 if (!SQL_SUCCEEDED(sr))
365 throw IOException("ODBC StorageService failed to disable auto-commit mode.");
366 sr = SQLSetConnectAttr(handle, SQL_ATTR_TXN_ISOLATION, (SQLPOINTER)SQL_TXN_SERIALIZABLE, NULL);
367 if (!SQL_SUCCEEDED(sr))
368 throw IOException("ODBC StorageService failed to enable transaction isolation.");
373 SQLHSTMT ODBCStorageService::getHSTMT(SQLHDBC conn)
376 SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
377 if (!SQL_SUCCEEDED(sr)) {
378 m_log.error("failed to allocate statement handle");
379 log_error(conn, SQL_HANDLE_DBC);
380 throw IOException("ODBC StorageService failed to allocate a statement handle.");
385 pair<int,int> ODBCStorageService::getVersion(SQLHDBC conn)
387 // Grab the version number from the database.
388 ODBCStatement stmt(getHSTMT(conn));
390 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
391 if (!SQL_SUCCEEDED(sr)) {
392 m_log.error("failed to read version from database");
393 log_error(stmt, SQL_HANDLE_STMT);
394 throw IOException("ODBC StorageService failed to read version from database.");
399 SQLBindCol(stmt,1,SQL_C_SLONG,&major,0,NULL);
400 SQLBindCol(stmt,2,SQL_C_SLONG,&minor,0,NULL);
402 if ((sr=SQLFetch(stmt)) != SQL_NO_DATA)
403 return pair<int,int>(major,minor);
405 m_log.error("no rows returned in version query");
406 throw IOException("ODBC StorageService failed to read version from database.");
409 bool ODBCStorageService::createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration)
412 xmltooling::NDC ndc("createRow");
416 timestampFromTime(expiration, timebuf);
418 // Get statement handle.
419 ODBCConn conn(getHDBC());
420 ODBCStatement stmt(getHSTMT(conn));
422 // Prepare and exectute insert statement.
423 char *scontext = makeSafeSQL(context);
424 char *skey = makeSafeSQL(key);
425 char *svalue = makeSafeSQL(value);
426 string q = string("INSERT ") + table + " VALUES ('" + scontext + "','" + skey + "'," + timebuf + ",1,'" + svalue + "')";
427 freeSafeSQL(scontext, context);
428 freeSafeSQL(skey, key);
429 freeSafeSQL(svalue, value);
430 m_log.debug("SQL: %s", q.c_str());
432 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
433 if (!SQL_SUCCEEDED(sr)) {
434 m_log.error("insert record failed (t=%s, c=%s, k=%s)", table, context, key);
435 if (log_error(stmt, SQL_HANDLE_STMT, "23000"))
436 return false; // supposedly integrity violation?
437 throw IOException("ODBC StorageService failed to insert record.");
442 int ODBCStorageService::readRow(
443 const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int version, bool text
447 xmltooling::NDC ndc("readRow");
450 // Get statement handle.
451 ODBCConn conn(getHDBC());
452 ODBCStatement stmt(getHSTMT(conn));
454 // Prepare and exectute select statement.
456 timestampFromTime(time(NULL), timebuf);
457 char *scontext = makeSafeSQL(context);
458 char *skey = makeSafeSQL(key);
460 q << "SELECT version";
464 q << ",CASE version WHEN " << version << " THEN NULL ELSE value END";
465 q << " FROM " << table << " WHERE context='" << scontext << "' AND id='" << skey << "' AND expires > " << timebuf;
466 freeSafeSQL(scontext, context);
467 freeSafeSQL(skey, key);
468 if (m_log.isDebugEnabled())
469 m_log.debug("SQL: %s", q.str().c_str());
471 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
472 if (!SQL_SUCCEEDED(sr)) {
473 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
474 log_error(stmt, SQL_HANDLE_STMT);
475 throw IOException("ODBC StorageService search failed.");
479 SQL_TIMESTAMP_STRUCT expiration;
481 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
483 SQLBindCol(stmt,2,SQL_C_TYPE_TIMESTAMP,&expiration,0,NULL);
485 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA)
489 *pexpiration = timeFromTimestamp(expiration);
492 return version; // nothing's changed, so just echo back the version
496 SQLCHAR buf[LONGDATA_BUFLEN];
497 while ((sr=SQLGetData(stmt,pexpiration ? 3 : 2,SQL_C_CHAR,buf,sizeof(buf),&len)) != SQL_NO_DATA) {
498 if (!SQL_SUCCEEDED(sr)) {
499 m_log.error("error while reading text field from result set");
500 log_error(stmt, SQL_HANDLE_STMT);
501 throw IOException("ODBC StorageService search failed to read data from result set.");
503 pvalue->append((char*)buf);
510 int ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration, int version)
513 xmltooling::NDC ndc("updateRow");
516 if (!value && !expiration)
517 throw IOException("ODBC StorageService given invalid update instructions.");
519 // Get statement handle.
520 ODBCConn conn(getHDBC());
521 ODBCStatement stmt(getHSTMT(conn));
523 // First, fetch the current version for later, which also ensures the record still exists.
525 timestampFromTime(time(NULL), timebuf);
526 char *scontext = makeSafeSQL(context);
527 char *skey = makeSafeSQL(key);
528 string q("SELECT version FROM ");
529 q = q + table + " WHERE context='" + scontext + "' AND id='" + key + "' AND expires > " + timebuf;
531 m_log.debug("SQL: %s", q.c_str());
533 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
534 if (!SQL_SUCCEEDED(sr)) {
535 freeSafeSQL(scontext, context);
536 freeSafeSQL(skey, key);
537 m_log.error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
538 log_error(stmt, SQL_HANDLE_STMT);
539 throw IOException("ODBC StorageService search failed.");
543 SQLBindCol(stmt,1,SQL_C_SSHORT,&ver,0,NULL);
544 if ((sr=SQLFetch(stmt)) == SQL_NO_DATA) {
545 freeSafeSQL(scontext, context);
546 freeSafeSQL(skey, key);
551 if (version > 0 && version != ver) {
552 freeSafeSQL(scontext, context);
553 freeSafeSQL(skey, key);
557 // Prepare and exectute update statement.
558 q = string("UPDATE ") + table + " SET ";
561 char *svalue = makeSafeSQL(value);
562 q = q + "value='" + svalue + "'" + ",version=version+1";
563 freeSafeSQL(svalue, value);
567 timestampFromTime(expiration, timebuf);
570 q = q + "expires = " + timebuf;
573 q = q + " WHERE context='" + scontext + "' AND id='" + key + "'";
574 freeSafeSQL(scontext, context);
575 freeSafeSQL(skey, key);
577 m_log.debug("SQL: %s", q.c_str());
578 sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
580 return 0; // went missing?
581 else if (!SQL_SUCCEEDED(sr)) {
582 m_log.error("update of record failed (t=%s, c=%s, k=%s", table, context, key);
583 log_error(stmt, SQL_HANDLE_STMT);
584 throw IOException("ODBC StorageService failed to update record.");
590 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
593 xmltooling::NDC ndc("deleteRow");
596 // Get statement handle.
597 ODBCConn conn(getHDBC());
598 ODBCStatement stmt(getHSTMT(conn));
600 // Prepare and execute delete statement.
601 char *scontext = makeSafeSQL(context);
602 char *skey = makeSafeSQL(key);
603 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND id='" + skey + "'";
604 freeSafeSQL(scontext, context);
605 freeSafeSQL(skey, key);
606 m_log.debug("SQL: %s", q.c_str());
608 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
611 else if (!SQL_SUCCEEDED(sr)) {
612 m_log.error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
613 log_error(stmt, SQL_HANDLE_STMT);
614 throw IOException("ODBC StorageService failed to delete record.");
621 void ODBCStorageService::cleanup()
624 xmltooling::NDC ndc("cleanup");
627 Mutex* mutex = Mutex::create();
631 m_log.info("cleanup thread started... running every %d secs", m_cleanupInterval);
634 shutdown_wait->timedwait(mutex, m_cleanupInterval);
640 catch (exception& ex) {
641 m_log.error("cleanup thread swallowed exception: %s", ex.what());
645 m_log.info("cleanup thread exiting...");
652 void* ODBCStorageService::cleanup_fn(void* cache_p)
654 ODBCStorageService* cache = (ODBCStorageService*)cache_p;
657 // First, let's block all signals
658 Thread::mask_all_signals();
661 // Now run the cleanup process.
666 void ODBCStorageService::updateContext(const char *table, const char* context, time_t expiration)
669 xmltooling::NDC ndc("updateContext");
672 // Get statement handle.
673 ODBCConn conn(getHDBC());
674 ODBCStatement stmt(getHSTMT(conn));
677 timestampFromTime(expiration, timebuf);
680 timestampFromTime(time(NULL), nowbuf);
682 char *scontext = makeSafeSQL(context);
684 q = q + table + " SET expires = " + timebuf + " WHERE context='" + scontext + "' AND expires > " + nowbuf;
685 freeSafeSQL(scontext, context);
687 m_log.debug("SQL: %s", q.c_str());
689 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
690 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
691 m_log.error("error updating records (t=%s, c=%s)", table, context ? context : "all");
692 log_error(stmt, SQL_HANDLE_STMT);
693 throw IOException("ODBC StorageService failed to update context expiration.");
697 void ODBCStorageService::reap(const char *table, const char* context)
700 xmltooling::NDC ndc("reap");
703 // Get statement handle.
704 ODBCConn conn(getHDBC());
705 ODBCStatement stmt(getHSTMT(conn));
707 // Prepare and execute delete statement.
709 timestampFromTime(time(NULL), nowbuf);
712 char *scontext = makeSafeSQL(context);
713 q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires <= " + nowbuf;
714 freeSafeSQL(scontext, context);
717 q = string("DELETE FROM ") + table + " WHERE expires <= " + nowbuf;
719 m_log.debug("SQL: %s", q.c_str());
721 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
722 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
723 m_log.error("error expiring records (t=%s, c=%s)", table, context ? context : "all");
724 log_error(stmt, SQL_HANDLE_STMT);
725 throw IOException("ODBC StorageService failed to purge expired records.");
729 void ODBCStorageService::deleteContext(const char *table, const char* context)
732 xmltooling::NDC ndc("deleteContext");
735 // Get statement handle.
736 ODBCConn conn(getHDBC());
737 ODBCStatement stmt(getHSTMT(conn));
739 // Prepare and execute delete statement.
740 char *scontext = makeSafeSQL(context);
741 string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
742 freeSafeSQL(scontext, context);
743 m_log.debug("SQL: %s", q.c_str());
745 SQLRETURN sr=SQLExecDirect(stmt, (SQLCHAR*)q.c_str(), SQL_NTS);
746 if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
747 m_log.error("error deleting context (t=%s, c=%s)", table, context);
748 log_error(stmt, SQL_HANDLE_STMT);
749 throw IOException("ODBC StorageService failed to delete context.");
753 extern "C" int ODBCSTORE_EXPORTS xmltooling_extension_init(void*)
755 // Register this SS type
756 XMLToolingConfig::getConfig().StorageServiceManager.registerFactory("ODBC", ODBCStorageServiceFactory);
760 extern "C" void ODBCSTORE_EXPORTS xmltooling_extension_term()
762 XMLToolingConfig::getConfig().StorageServiceManager.deregisterFactory("ODBC");