]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_mssql.cpp
13f25d4fc2ef31f3eb54f52e4284f28b866b37b0
[user/henk/code/inspircd.git] / src / modules / extra / m_mssql.cpp
1 /*               +------------------------------------+
2  *               | Inspire Internet Relay Chat Daemon |
3  *               +------------------------------------+
4  *
5  *      InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *                        the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include <tds.h>
16 #include <tdsconvert.h>
17 #include "users.h"
18 #include "channels.h"
19 #include "modules.h"
20
21 #include "m_sqlv2.h"
22
23 /* $ModDesc: MsSQL provider */
24 /* $CompileFlags: exec("grep VERSION_NO /usr/include/tdsver.h 2>/dev/null | perl -e 'print "-D_TDSVER=".((<> =~ /freetds v(\d+\.\d+)/i) ? $1*100 : 0);'") */
25 /* $LinkerFlags: -ltds */
26 /* $ModDep: m_sqlv2.h */
27
28 class SQLConn;
29 class MsSQLResult;
30 class ModuleMsSQL;
31
32 typedef std::map<std::string, SQLConn*> ConnMap;
33 typedef std::deque<MsSQLResult*> ResultQueue;
34
35 unsigned long count(const char * const str, char a)
36 {
37         unsigned long n = 0;
38         for (const char *p = str; *p; ++p)
39         {
40                 if (*p == '?')
41                         ++n;
42         }
43         return n;
44 }
45
46 ConnMap connections;
47 Mutex* ResultsMutex;
48 Mutex* LoggingMutex;
49
50 class QueryThread : public SocketThread
51 {
52   private:
53         ModuleMsSQL* const Parent;
54   public:
55         QueryThread(ModuleMsSQL* mod) : Parent(mod) { }
56         ~QueryThread() { }
57         virtual void Run();
58         virtual void OnNotify();
59 };
60
61 class MsSQLResult : public SQLresult
62 {
63  private:
64         int currentrow;
65         int rows;
66         int cols;
67
68         std::vector<std::string> colnames;
69         std::vector<SQLfieldList> fieldlists;
70         SQLfieldList emptyfieldlist;
71
72         SQLfieldList* fieldlist;
73         SQLfieldMap* fieldmap;
74
75  public:
76         MsSQLResult(Module* self, Module* to, unsigned int rid)
77         : SQLresult(self, to, rid), currentrow(0), rows(0), cols(0), fieldlist(NULL), fieldmap(NULL)
78         {
79         }
80
81         ~MsSQLResult()
82         {
83         }
84
85         void AddRow(int colsnum, char **dat, char **colname)
86         {
87                 colnames.clear();
88                 cols = colsnum;
89                 for (int i = 0; i < colsnum; i++)
90                 {
91                         fieldlists.resize(fieldlists.size()+1);
92                         colnames.push_back(colname[i]);
93                         SQLfield sf(dat[i] ? dat[i] : "", dat[i] ? false : true);
94                         fieldlists[rows].push_back(sf);
95                 }
96                 rows++;
97         }
98
99         void UpdateAffectedCount()
100         {
101                 rows++;
102         }
103
104         virtual int Rows()
105         {
106                 return rows;
107         }
108
109         virtual int Cols()
110         {
111                 return cols;
112         }
113
114         virtual std::string ColName(int column)
115         {
116                 if (column < (int)colnames.size())
117                 {
118                         return colnames[column];
119                 }
120                 else
121                 {
122                         throw SQLbadColName();
123                 }
124                 return "";
125         }
126
127         virtual int ColNum(const std::string &column)
128         {
129                 for (unsigned int i = 0; i < colnames.size(); i++)
130                 {
131                         if (column == colnames[i])
132                                 return i;
133                 }
134                 throw SQLbadColName();
135                 return 0;
136         }
137
138         virtual SQLfield GetValue(int row, int column)
139         {
140                 if ((row >= 0) && (row < rows) && (column >= 0) && (column < Cols()))
141                 {
142                         return fieldlists[row][column];
143                 }
144
145                 throw SQLbadColName();
146
147                 /* XXX: We never actually get here because of the throw */
148                 return SQLfield("",true);
149         }
150
151         virtual SQLfieldList& GetRow()
152         {
153                 if (currentrow < rows)
154                         return fieldlists[currentrow];
155                 else
156                         return emptyfieldlist;
157         }
158
159         virtual SQLfieldMap& GetRowMap()
160         {
161                 /* In an effort to reduce overhead we don't actually allocate the map
162                  * until the first time it's needed...so...
163                  */
164                 if(fieldmap)
165                 {
166                         fieldmap->clear();
167                 }
168                 else
169                 {
170                         fieldmap = new SQLfieldMap;
171                 }
172
173                 if (currentrow < rows)
174                 {
175                         for (int i = 0; i < Cols(); i++)
176                         {
177                                 fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
178                         }
179                         currentrow++;
180                 }
181
182                 return *fieldmap;
183         }
184
185         virtual SQLfieldList* GetRowPtr()
186         {
187                 fieldlist = new SQLfieldList();
188
189                 if (currentrow < rows)
190                 {
191                         for (int i = 0; i < Rows(); i++)
192                         {
193                                 fieldlist->push_back(fieldlists[currentrow][i]);
194                         }
195                         currentrow++;
196                 }
197                 return fieldlist;
198         }
199
200         virtual SQLfieldMap* GetRowMapPtr()
201         {
202                 fieldmap = new SQLfieldMap();
203
204                 if (currentrow < rows)
205                 {
206                         for (int i = 0; i < Cols(); i++)
207                         {
208                                 fieldmap->insert(std::make_pair(colnames[i],GetValue(currentrow, i)));
209                         }
210                         currentrow++;
211                 }
212
213                 return fieldmap;
214         }
215
216         virtual void Free(SQLfieldMap* fm)
217         {
218                 delete fm;
219         }
220
221         virtual void Free(SQLfieldList* fl)
222         {
223                 delete fl;
224         }
225 };
226
227 class SQLConn : public classbase
228 {
229  private:
230         ResultQueue results;
231         Module* mod;
232         SQLhost host;
233         TDSLOGIN* login;
234         TDSSOCKET* sock;
235         TDSCONTEXT* context;
236
237  public:
238         QueryQueue queue;
239
240         SQLConn(Module* m, const SQLhost& hi)
241         : mod(m), host(hi), login(NULL), sock(NULL), context(NULL)
242         {
243                 if (OpenDB())
244                 {
245                         std::string query("USE " + host.name);
246                         if (tds_submit_query(sock, query.c_str()) == TDS_SUCCEED)
247                         {
248                                 if (tds_process_simple_query(sock) != TDS_SUCCEED)
249                                 {
250                                         LoggingMutex->Lock();
251                                         ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: Could not select database " + host.name + " for DB with id: " + host.id);
252                                         LoggingMutex->Unlock();
253                                         CloseDB();
254                                 }
255                         }
256                         else
257                         {
258                                 LoggingMutex->Lock();
259                                 ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: Could not select database " + host.name + " for DB with id: " + host.id);
260                                 LoggingMutex->Unlock();
261                                 CloseDB();
262                         }
263                 }
264                 else
265                 {
266                         LoggingMutex->Lock();
267                         ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: Could not connect to DB with id: " + host.id);
268                         LoggingMutex->Unlock();
269                         CloseDB();
270                 }
271         }
272
273         ~SQLConn()
274         {
275                 CloseDB();
276         }
277
278         SQLerror Query(SQLrequest* req)
279         {
280                 if (!sock)
281                         return SQLerror(SQL_BAD_CONN, "Socket was NULL, check if SQL server is running.");
282
283                 /* Pointer to the buffer we screw around with substitution in */
284                 char* query;
285
286                 /* Pointer to the current end of query, where we append new stuff */
287                 char* queryend;
288
289                 /* Total length of the unescaped parameters */
290                 unsigned long maxparamlen, paramcount;
291
292                 /* The length of the longest parameter */
293                 maxparamlen = 0;
294
295                 for(ParamL::iterator i = req->query.p.begin(); i != req->query.p.end(); i++)
296                 {
297                         if (i->size() > maxparamlen)
298                                 maxparamlen = i->size();
299                 }
300
301                 /* How many params are there in the query? */
302                 paramcount = count(req->query.q.c_str(), '?');
303
304                 /* This stores copy of params to be inserted with using numbered params 1;3B*/
305                 ParamL paramscopy(req->query.p);
306
307                 /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be.
308                  * sizeofquery + (maxtotalparamlength*2) + 1
309                  *
310                  * The +1 is for null-terminating the string
311                  */
312
313                 query = new char[req->query.q.length() + (maxparamlen*paramcount*2) + 1];
314                 queryend = query;
315
316                 for(unsigned long i = 0; i < req->query.q.length(); i++)
317                 {
318                         if(req->query.q[i] == '?')
319                         {
320                                 /* We found a place to substitute..what fun.
321                                  * use mssql calls to escape and write the
322                                  * escaped string onto the end of our query buffer,
323                                  * then we "just" need to make sure queryend is
324                                  * pointing at the right place.
325                                  */
326
327                                 /* Is it numbered parameter?
328                                  */
329
330                                 bool numbered;
331                                 numbered = false;
332
333                                 /* Numbered parameter number :|
334                                  */
335                                 unsigned int paramnum;
336                                 paramnum = 0;
337
338                                 /* Let's check if it's a numbered param. And also calculate it's number.
339                                  */
340
341                                 while ((i < req->query.q.length() - 1) && (req->query.q[i+1] >= '0') && (req->query.q[i+1] <= '9'))
342                                 {
343                                         numbered = true;
344                                         ++i;
345                                         paramnum = paramnum * 10 + req->query.q[i] - '0';
346                                 }
347
348                                 if (paramnum > paramscopy.size() - 1)
349                                 {
350                                         /* index is out of range!
351                                          */
352                                         numbered = false;
353                                 }
354
355                                 if (numbered)
356                                 {
357                                         /* Custom escaping for this one. converting ' to '' should make SQL Server happy. Ugly but fast :]
358                                          */
359                                         char* escaped = new char[(paramscopy[paramnum].length() * 2) + 1];
360                                         char* escend = escaped;
361                                         for (std::string::iterator p = paramscopy[paramnum].begin(); p < paramscopy[paramnum].end(); p++)
362                                         {
363                                                 if (*p == '\'')
364                                                 {
365                                                         *escend = *p;
366                                                         escend++;
367                                                         *escend = *p;
368                                                 }
369                                                 *escend = *p;
370                                                 escend++;
371                                         }
372                                         *escend = 0;
373
374                                         for (char* n = escaped; *n; n++)
375                                         {
376                                                 *queryend = *n;
377                                                 queryend++;
378                                         }
379                                         delete[] escaped;
380                                 }
381                                 else if (req->query.p.size())
382                                 {
383                                         /* Custom escaping for this one. converting ' to '' should make SQL Server happy. Ugly but fast :]
384                                          */
385                                         char* escaped = new char[(req->query.p.front().length() * 2) + 1];
386                                         char* escend = escaped;
387                                         for (std::string::iterator p = req->query.p.front().begin(); p < req->query.p.front().end(); p++)
388                                         {
389                                                 if (*p == '\'')
390                                                 {
391                                                         *escend = *p;
392                                                         escend++;
393                                                         *escend = *p;
394                                                 }
395                                                 *escend = *p;
396                                                 escend++;
397                                         }
398                                         *escend = 0;
399
400                                         for (char* n = escaped; *n; n++)
401                                         {
402                                                 *queryend = *n;
403                                                 queryend++;
404                                         }
405                                         delete[] escaped;
406                                         req->query.p.pop_front();
407                                 }
408                                 else
409                                         break;
410                         }
411                         else
412                         {
413                                 *queryend = req->query.q[i];
414                                 queryend++;
415                         }
416                 }
417                 *queryend = 0;
418                 req->query.q = query;
419
420                 MsSQLResult* res = new MsSQLResult((Module*)mod, req->source, req->id);
421                 res->dbid = host.id;
422                 res->query = req->query.q;
423
424                 char* msquery = strdup(req->query.q.data());
425                 LoggingMutex->Lock();
426                 ServerInstance->Logs->Log("m_mssql",DEBUG,"doing Query: %s",msquery);
427                 LoggingMutex->Unlock();
428                 if (tds_submit_query(sock, msquery) != TDS_SUCCEED)
429                 {
430                         std::string error("failed to execute: "+std::string(req->query.q.data()));
431                         delete[] query;
432                         delete res;
433                         free(msquery);
434                         return SQLerror(SQL_QSEND_FAIL, error);
435                 }
436                 delete[] query;
437                 free(msquery);
438
439                 int tds_res;
440                 while (tds_process_tokens(sock, &tds_res, NULL, TDS_TOKEN_RESULTS) == TDS_SUCCEED)
441                 {
442                         //ServerInstance->Logs->Log("m_mssql",DEBUG,"<******> result type: %d", tds_res);
443                         //ServerInstance->Logs->Log("m_mssql",DEBUG,"AFFECTED ROWS: %d", sock->rows_affected);
444                         switch (tds_res)
445                         {
446                                 case TDS_ROWFMT_RESULT:
447                                         break;
448
449                                 case TDS_DONE_RESULT:
450                                         if (sock->rows_affected > -1)
451                                         {
452                                                 for (int c = 0; c < sock->rows_affected; c++)  res->UpdateAffectedCount();
453                                                 continue;
454                                         }
455                                         break;
456
457                                 case TDS_ROW_RESULT:
458                                         while (tds_process_tokens(sock, &tds_res, NULL, TDS_STOPAT_ROWFMT|TDS_RETURN_DONE|TDS_RETURN_ROW) == TDS_SUCCEED)
459                                         {
460                                                 if (tds_res != TDS_ROW_RESULT)
461                                                         break;
462
463                                                 if (!sock->current_results)
464                                                         continue;
465
466                                                 if (sock->res_info->row_count > 0)
467                                                 {
468                                                         int cols = sock->res_info->num_cols;
469                                                         char** name = new char*[MAXBUF];
470                                                         char** data = new char*[MAXBUF];
471                                                         for (int j=0; j<cols; j++)
472                                                         {
473                                                                 TDSCOLUMN* col = sock->current_results->columns[j];
474                                                                 name[j] = col->column_name;
475
476                                                                 int ctype;
477                                                                 int srclen;
478                                                                 unsigned char* src;
479                                                                 CONV_RESULT dres;
480                                                                 ctype = tds_get_conversion_type(col->column_type, col->column_size);
481 #if _TDSVER >= 82
482                                                                         src = col->column_data;
483 #else
484                                                                         src = &(sock->current_results->current_row[col->column_offset]);
485 #endif
486                                                                 srclen = col->column_cur_size;
487                                                                 tds_convert(sock->tds_ctx, ctype, (TDS_CHAR *) src, srclen, SYBCHAR, &dres);
488                                                                 data[j] = (char*)dres.ib;
489                                                         }
490                                                         ResultReady(res, cols, data, name);
491                                                 }
492                                         }
493                                         break;
494
495                                 default:
496                                         break;
497                         }
498                 }
499                 ResultsMutex->Lock();
500                 results.push_back(res);
501                 ResultsMutex->Unlock();
502                 return SQLerror();
503         }
504
505         static int HandleMessage(const TDSCONTEXT * pContext, TDSSOCKET * pTdsSocket, TDSMESSAGE * pMessage)
506         {
507                 SQLConn* sc = (SQLConn*)pContext->parent;
508                 LoggingMutex->Lock();
509                 ServerInstance->Logs->Log("m_mssql", DEBUG, "Message for DB with id: %s -> %s", sc->host.id.c_str(), pMessage->message);
510                 LoggingMutex->Unlock();
511                 return 0;
512         }
513
514         static int HandleError(const TDSCONTEXT * pContext, TDSSOCKET * pTdsSocket, TDSMESSAGE * pMessage)
515         {
516                 SQLConn* sc = (SQLConn*)pContext->parent;
517                 LoggingMutex->Lock();
518                 ServerInstance->Logs->Log("m_mssql", DEFAULT, "Error for DB with id: %s -> %s", sc->host.id.c_str(), pMessage->message);
519                 LoggingMutex->Unlock();
520                 return 0;
521         }
522
523         void ResultReady(MsSQLResult *res, int cols, char **data, char **colnames)
524         {
525                 res->AddRow(cols, data, colnames);
526         }
527
528         void AffectedReady(MsSQLResult *res)
529         {
530                 res->UpdateAffectedCount();
531         }
532
533         bool OpenDB()
534         {
535                 CloseDB();
536
537                 TDSCONNECTION* conn = NULL;
538
539                 login = tds_alloc_login();
540                 tds_set_app(login, "TSQL");
541                 tds_set_library(login,"TDS-Library");
542                 tds_set_host(login, "");
543                 tds_set_server(login, host.host.c_str());
544                 tds_set_server_addr(login, host.host.c_str());
545                 tds_set_user(login, host.user.c_str());
546                 tds_set_passwd(login, host.pass.c_str());
547                 tds_set_port(login, host.port);
548                 tds_set_packet(login, 512);
549
550                 context = tds_alloc_context(this);
551                 context->msg_handler = HandleMessage;
552                 context->err_handler = HandleError;
553
554                 sock = tds_alloc_socket(context, 512);
555                 tds_set_parent(sock, NULL);
556
557                 conn = tds_read_config_info(NULL, login, context->locale);
558
559                 if (tds_connect(sock, conn) == TDS_SUCCEED)
560                 {
561                         tds_free_connection(conn);
562                         return 1;
563                 }
564                 tds_free_connection(conn);
565                 return 0;
566         }
567
568         void CloseDB()
569         {
570                 if (sock)
571                 {
572                         tds_free_socket(sock);
573                         sock = NULL;
574                 }
575                 if (context)
576                 {
577                         tds_free_context(context);
578                         context = NULL;
579                 }
580                 if (login)
581                 {
582                         tds_free_login(login);
583                         login = NULL;
584                 }
585         }
586
587         SQLhost GetConfHost()
588         {
589                 return host;
590         }
591
592         void SendResults()
593         {
594                 while (results.size())
595                 {
596                         MsSQLResult* res = results[0];
597                         ResultsMutex->Lock();
598                         if (res->dest)
599                         {
600                                 res->Send();
601                         }
602                         else
603                         {
604                                 /* If the client module is unloaded partway through a query then the provider will set
605                                  * the pointer to NULL. We cannot just cancel the query as the result will still come
606                                  * through at some point...and it could get messy if we play with invalid pointers...
607                                  */
608                                 delete res;
609                         }
610                         results.pop_front();
611                         ResultsMutex->Unlock();
612                 }
613         }
614
615         void ClearResults()
616         {
617                 while (results.size())
618                 {
619                         MsSQLResult* res = results[0];
620                         delete res;
621                         results.pop_front();
622                 }
623         }
624
625         void DoLeadingQuery()
626         {
627                 SQLrequest* req = queue.front();
628                 req->error = Query(req);
629         }
630
631 };
632
633
634 class ModuleMsSQL : public Module
635 {
636  private:
637         unsigned long currid;
638         QueryThread* queryDispatcher;
639         ServiceProvider sqlserv;
640
641  public:
642         ModuleMsSQL()
643         : currid(0), sqlserv(this, "SQL/mssql", SERVICE_DATA)
644         {
645                 LoggingMutex = new Mutex();
646                 ResultsMutex = new Mutex();
647
648                 ReadConf();
649
650                 queryDispatcher = new QueryThread(this);
651                 ServerInstance->Threads->Start(queryDispatcher);
652
653                 Implementation eventlist[] = { I_OnRehash };
654                 ServerInstance->Modules->Attach(eventlist, this, 1);
655                 ServerInstance->Modules->AddService(sqlserv);
656         }
657
658         virtual ~ModuleMsSQL()
659         {
660                 queryDispatcher->join();
661                 delete queryDispatcher;
662                 ClearQueue();
663                 ClearAllConnections();
664
665                 delete LoggingMutex;
666                 delete ResultsMutex;
667         }
668
669         void SendQueue()
670         {
671                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
672                 {
673                         iter->second->SendResults();
674                 }
675         }
676
677         void ClearQueue()
678         {
679                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
680                 {
681                         iter->second->ClearResults();
682                 }
683         }
684
685         bool HasHost(const SQLhost &host)
686         {
687                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
688                 {
689                         if (host == iter->second->GetConfHost())
690                                 return true;
691                 }
692                 return false;
693         }
694
695         bool HostInConf(const SQLhost &h)
696         {
697                 ConfigReader conf;
698                 for(int i = 0; i < conf.Enumerate("database"); i++)
699                 {
700                         SQLhost host;
701                         host.id         = conf.ReadValue("database", "id", i);
702                         host.host       = conf.ReadValue("database", "hostname", i);
703                         host.port       = conf.ReadInteger("database", "port", "1433", i, true);
704                         host.name       = conf.ReadValue("database", "name", i);
705                         host.user       = conf.ReadValue("database", "username", i);
706                         host.pass       = conf.ReadValue("database", "password", i);
707                         if (h == host)
708                                 return true;
709                 }
710                 return false;
711         }
712
713         void ReadConf()
714         {
715                 ClearOldConnections();
716
717                 ConfigReader conf;
718                 for(int i = 0; i < conf.Enumerate("database"); i++)
719                 {
720                         SQLhost host;
721
722                         host.id         = conf.ReadValue("database", "id", i);
723                         host.host       = conf.ReadValue("database", "hostname", i);
724                         host.port       = conf.ReadInteger("database", "port", "1433", i, true);
725                         host.name       = conf.ReadValue("database", "name", i);
726                         host.user       = conf.ReadValue("database", "username", i);
727                         host.pass       = conf.ReadValue("database", "password", i);
728
729                         if (HasHost(host))
730                                 continue;
731
732                         this->AddConn(host);
733                 }
734         }
735
736         void AddConn(const SQLhost& hi)
737         {
738                 if (HasHost(hi))
739                 {
740                         LoggingMutex->Lock();
741                         ServerInstance->Logs->Log("m_mssql",DEFAULT, "WARNING: A MsSQL connection with id: %s already exists. Aborting database open attempt.", hi.id.c_str());
742                         LoggingMutex->Unlock();
743                         return;
744                 }
745
746                 SQLConn* newconn;
747
748                 newconn = new SQLConn(this, hi);
749
750                 connections.insert(std::make_pair(hi.id, newconn));
751         }
752
753         void ClearOldConnections()
754         {
755                 ConnMap::iterator iter,safei;
756                 for (iter = connections.begin(); iter != connections.end(); iter++)
757                 {
758                         if (!HostInConf(iter->second->GetConfHost()))
759                         {
760                                 delete iter->second;
761                                 safei = iter;
762                                 --iter;
763                                 connections.erase(safei);
764                         }
765                 }
766         }
767
768         void ClearAllConnections()
769         {
770                 ConnMap::iterator i;
771                 while ((i = connections.begin()) != connections.end())
772                 {
773                         connections.erase(i);
774                         delete i->second;
775                 }
776         }
777
778         virtual void OnRehash(User* user)
779         {
780                 queryDispatcher->LockQueue();
781                 ReadConf();
782                 queryDispatcher->UnlockQueueWakeup();
783         }
784
785         void OnRequest(Request& request)
786         {
787                 if(strcmp(SQLREQID, request.id) == 0)
788                 {
789                         SQLrequest* req = (SQLrequest*)&request;
790
791                         queryDispatcher->LockQueue();
792
793                         ConnMap::iterator iter;
794
795                         if((iter = connections.find(req->dbid)) != connections.end())
796                         {
797                                 req->id = NewID();
798                                 iter->second->queue.push(new SQLrequest(*req));
799                         }
800                         else
801                         {
802                                 req->error.Id(SQL_BAD_DBID);
803                         }
804                         queryDispatcher->UnlockQueueWakeup();
805                 }
806         }
807
808         unsigned long NewID()
809         {
810                 if (currid+1 == 0)
811                         currid++;
812
813                 return ++currid;
814         }
815
816         virtual Version GetVersion()
817         {
818                 return Version("MsSQL provider", VF_VENDOR);
819         }
820
821 };
822
823 void QueryThread::OnNotify()
824 {
825         Parent->SendQueue();
826 }
827
828 void QueryThread::Run()
829 {
830         this->LockQueue();
831         while (this->GetExitFlag() == false)
832         {
833                 SQLConn* conn = NULL;
834                 for (ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
835                 {
836                         if (i->second->queue.totalsize())
837                         {
838                                 conn = i->second;
839                                 break;
840                         }
841                 }
842                 if (conn)
843                 {
844                         this->UnlockQueue();
845                         conn->DoLeadingQuery();
846                         this->NotifyParent();
847                         this->LockQueue();
848                         conn->queue.pop();
849                 }
850                 else
851                 {
852                         this->WaitForQueue();
853                 }
854         }
855         this->UnlockQueue();
856 }
857
858 MODULE_INIT(ModuleMsSQL)