source: trunk/Mars/msql/MSQLServer.cc@ 15418

Last change on this file since 15418 was 15193, checked in by tbretz, 12 years ago
Remove leading spaces from url, user and password
File size: 20.4 KB
Line 
1/* ======================================================================== *\
2!
3! *
4! * This file is part of MARS, the MAGIC Analysis and Reconstruction
5! * Software. It is distributed to you in the hope that it can be a useful
6! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
7! * It is distributed WITHOUT ANY WARRANTY.
8! *
9! * Permission to use, copy, modify and distribute this software and its
10! * documentation for any purpose is hereby granted without fee,
11! * provided that the above copyright notice appear in all copies and
12! * that both that copyright notice and this permission notice appear
13! * in supporting documentation. It is provided "as is" without express
14! * or implied warranty.
15! *
16!
17!
18! Author(s): Thomas Bretz 2/2004 <mailto:tbretz@astro.uni-wuerzburg.de>
19!
20! Copyright: MAGIC Software Development, 2000-2006
21!
22!
23\* ======================================================================== */
24
25////////////////////////////////////////////////////////////////////////
26//
27// MSQLServer
28//
29// Using this instead of a TSQLServer gives the possibility to
30// browse a database server through the TBrowser and it will offer
31// many features usefull working with relational tables.
32//
33// Use it like TSQlServer:
34// new MSQLServer("mysql://localhost:3306", "hercules", "stdmagicpassword");
35// // now start your TBrowser
36// new TBrowser;
37//
38////////////////////////////////////////////////////////////////////////
39#include "MSQLServer.h"
40
41#include <iostream>
42#include <iomanip>
43#include <stdlib.h>
44
45#include <TROOT.h>
46#include <TMath.h>
47
48#include <TH1.h>
49#include <TEnv.h>
50#include <TPRegexp.h>
51
52#include <TSQLResult.h>
53#include <TSQLServer.h>
54#include <TSQLRow.h>
55
56#include <TBrowser.h>
57
58#include <TObjString.h>
59#include <TObjArray.h>
60
61ClassImp(MSQLServer);
62
63using namespace std;
64
65// --------------------------------------------------------------------------
66//
67// Used in virtual function TObject::Browse() to create the
68//
69void MSQLServer::BrowseColumn(TBrowser *b) /*FOLD00*/
70{
71 if (!fServ)
72 return;
73
74 const TString query0(Form("EXPLAIN %s.%s %s", (const char*)fDataBase, (const char*)fTable, (const char*)fColumn));
75 const TString query1(Form("SELECT %s FROM %s.%s", (const char*)fColumn, (const char*)fDataBase, (const char*)fTable));
76
77 //cout << query0 << endl;
78 TSQLResult *res = fServ->Query(query0);
79 if (!res)
80 {
81 cout << "query - failed: " << query0 << endl;
82 return;
83 }
84
85 TSQLRow *row=res->Next();
86 const TString desc(row ? (*row)[1] : "");
87
88 if (row)
89 delete row;
90
91 delete res;
92
93 const Bool_t isnum =
94 !desc.Contains("char", TString::kIgnoreCase) &&
95 !desc.Contains("text", TString::kIgnoreCase);
96
97 cout << query1 << endl;
98 res = fServ->Query(query1);
99 if (!res)
100 {
101 cout << "query - failed: " << query1 << endl;
102 return;
103 }
104
105 TArrayD arr(2);
106 Int_t num=0;
107 Double_t max=0;
108 Double_t min=0;
109 Double_t sum=0;
110 Double_t sqr=0;
111
112 while ((row=res->Next()))
113 {
114 const TString row0((*row)[0]);
115
116 if (!isnum)
117 {
118 cout << row0 << endl;
119 continue;
120 }
121
122 if (num==arr.GetSize())
123 arr.Set(arr.GetSize()*2);
124
125 arr[num] = atof(row0.Data());
126
127 if (num==0)
128 min=max=arr[0];
129
130 if (arr[num]>max) max = arr[num];
131 if (arr[num]<min) min = arr[num];
132
133 sum += arr[num];
134 sqr += arr[num]*arr[num];
135
136 num++;
137 }
138
139 delete res;
140
141 if (!isnum)
142 return;
143
144 if (max==min) max += 1;
145
146 Int_t num0 = 1;
147
148 if (num>0)
149 {
150 /*
151 cout << "Num: " << num << endl;
152 cout << "Mean: " << sum/num << endl;
153 cout << "Range: " << max-min << endl;
154 cout << "RMS: " << TMath::Sqrt(sqr/num-sum*sum/num/num) << endl;
155 */
156
157 num0 = (Int_t)((max-min)*40/TMath::Sqrt(sqr/num-sum*sum/num/num));
158 }
159
160 const TString title(Form("#splitline{%s}{<%s>}", (const char*)query1, (const char*)desc));
161
162 TH1F *hist=new TH1F(fColumn, title, num0, min, max);
163 for (int i=0; i<num; i++)
164 hist->Fill(arr[i]);
165
166 //cout << "Done." << endl;
167
168 hist->Draw();
169 hist->SetBit(kCanDelete);
170}
171
172void MSQLServer::BrowseTable(TBrowser *b) /*FOLD00*/
173{
174 if (!fServ)
175 return;
176
177 TSQLResult *res = fServ->GetColumns(fDataBase, fTable);
178 if (!res)
179 return;
180
181 TSQLRow *row;
182 while ((row=res->Next()))
183 {
184 TString row0((*row)[0]);
185 delete row;
186
187 MSQLServer *sql = (MSQLServer*)fList.FindObject(Form("%s/%s/%s", (const char*)fDataBase, (const char*)fTable, (const char*)row0));
188 if (!sql)
189 {
190 sql = new MSQLColumn(fServ, fDataBase, fTable, row0);
191 fList.Add(sql);
192 }
193 b->Add(sql, row0);
194 }
195}
196
197void MSQLServer::BrowseDataBase(TBrowser *b) /*FOLD00*/
198{
199 if (!fServ)
200 return;
201
202 TSQLResult *res = fServ->GetTables(fDataBase);
203 if (!res)
204 return;
205
206 TSQLRow *row;
207 while ((row=res->Next()))
208 {
209 TString row0((*row)[0]);
210 delete row;
211
212 MSQLServer *sql = (MSQLServer*)fList.FindObject(Form("%s/%s", (const char*)fDataBase, (const char*)row0));
213 if (!sql)
214 {
215 sql = new MSQLServer(fServ, fDataBase, row0);
216 fList.Add(sql);
217 }
218 b->Add(sql, row0);
219 }
220}
221
222void MSQLServer::BrowseServer(TBrowser *b) /*FOLD00*/
223{
224 if (!fServ)
225 return;
226
227 TSQLResult *res = fServ->GetDataBases();
228 if (!res)
229 return;
230
231 TSQLRow *row;
232 while ((row=res->Next()))
233 {
234 const TString row0((*row)[0]);
235 delete row;
236
237 MSQLServer *sql = (MSQLServer*)fList.FindObject(row0);
238 if (!sql)
239 {
240 sql = new MSQLServer(fServ, row0);
241 fList.Add(sql);
242 }
243 b->Add(sql, row0);
244 }
245}
246
247void MSQLServer::PrintLine(const TArrayI &max) /*FOLD00*/
248{
249 cout << "+" << setfill('-');
250 for (int i=0; i<max.GetSize(); i++)
251 cout << setw(max[i]+1) << "-" << "-+";
252 cout << endl;
253}
254
255void MSQLServer::PrintTable(TSQLResult &res) /*FOLD00*/
256{
257 Int_t n = res.GetFieldCount();
258
259 TArrayI max(n);
260
261 for (int i=0; i<n; i++)
262 max[i] = strlen(res.GetFieldName(i));
263
264 TSQLRow *row;
265
266 TList rows;
267 rows.SetOwner();
268
269 while ((row=res.Next()))
270 {
271 for (int i=0; i<n; i++)
272 max[i] = TMath::Max((ULong_t)max[i], row->GetFieldLength(i));
273 rows.Add(row);
274 }
275
276 cout << endl;
277
278 PrintLine(max);
279
280 cout << "|" << setfill(' ');
281 for (int i=0; i<n; i++)
282 cout << setw(max[i]+1) << res.GetFieldName(i) << " |";
283 cout << endl;
284
285 PrintLine(max);
286
287 cout << setfill(' ');
288 TIter Next(&rows);
289 while ((row=(TSQLRow*)Next()))
290 {
291 cout << "|";
292 for (int i=0; i<n; i++)
293 {
294 const char *c = (*row)[i];
295 cout << setw(max[i]+1) << (c?c:"") << " |";
296 }
297 cout << endl;
298 }
299
300 PrintLine(max);
301}
302
303TString MSQLServer::GetFields() const /*FOLD00*/
304{
305 if (!fServ)
306 return "";
307
308 TSQLResult *res = fServ->GetColumns(fDataBase, fTable);
309 if (!res)
310 return "";
311
312 TString fields;
313
314 TSQLRow *row;
315
316 TList rows;
317 rows.SetOwner();
318
319 while ((row=res->Next()))
320 rows.Add(row);
321
322 TIter Next(&rows);
323 while ((row=(TSQLRow*)Next()))
324 {
325 fields += (*row)[0];
326 if (row!=rows.Last())
327 fields += ", ";
328 }
329
330 return fields;
331}
332
333void MSQLServer::PrintQuery(const char *query) const /*FOLD00*/
334{
335 if (!fServ)
336 return;
337
338 TSQLResult *res = fServ->Query(query);
339 if (res)
340 {
341 PrintTable(*res);
342 delete res;
343 }
344 else
345 cout << "Query failed: " << query << endl;
346}
347
348void MSQLServer::Print(Option_t *o) const /*FOLD00*/
349{
350 switch (fType)
351 {
352 case kIsServer:
353 PrintQuery("SHOW DATABASES");
354 break;
355
356 case kIsDataBase:
357 PrintQuery(Form("SHOW TABLES FROM %s", (const char*)fDataBase));
358 break;
359
360 case kIsTable:
361 PrintQuery(Form("SELECT * FROM %s.%s", (const char*)fDataBase, (const char*)fTable));
362 break;
363
364 case kIsColumn:
365 PrintQuery(Form("SELECT %s FROM %s.%s", (const char*)fColumn, (const char*)fDataBase, (const char*)fTable));
366 break;
367
368 default:
369 break;
370 }
371}
372
373void MSQLServer::ShowColumns() const /*FOLD00*/
374{
375 switch (fType)
376 {
377 case kIsTable:
378 PrintQuery(Form("SHOW FULl COLUMNS FROM %s.%s", (const char*)fDataBase, (const char*)fTable));
379 break;
380
381 case kIsColumn:
382 PrintQuery(Form("SHOW FULl COLUMNS FROM %s.%s LIKE %s", (const char*)fDataBase, (const char*)fTable, (const char*)fColumn));
383 break;
384
385 default:
386 //Print();
387 break;
388 }
389}
390
391void MSQLServer::ShowStatus() const /*FOLD00*/
392{
393 switch (fType)
394 {
395 case kIsServer:
396 PrintQuery("SHOW STATUS");
397 break;
398
399 case kIsDataBase:
400 PrintQuery(Form("SHOW TABLE STATUS FROM %s", (const char*)fDataBase));
401 break;
402
403 case kIsTable:
404 PrintQuery(Form("SHOW TABLE STATUS FROM %s LIKE %s", (const char*)fDataBase, (const char*)fTable));
405 break;
406
407 default:
408 break;
409 }
410}
411
412void MSQLServer::ShowTableIndex() const /*FOLD00*/
413{
414 switch (fType)
415 {
416 case kIsTable:
417 case kIsColumn:
418 PrintQuery(Form("SHOW INDEX FROM %s.%s", (const char*)fDataBase, (const char*)fTable));
419 break;
420
421 default:
422 break;
423 }
424}
425
426void MSQLServer::ShowTableCreate() const /*FOLD00*/
427{
428 switch (fType)
429 {
430 case kIsTable:
431 case kIsColumn:
432 PrintQuery(Form("SHOW CREATE TABLE %s.%s", (const char*)fDataBase, (const char*)fTable));
433 break;
434
435 default:
436 break;
437 }
438}
439
440void MSQLServer::Close(Option_t *option) /*FOLD00*/
441{
442 if (fType==kIsServer && fServ)
443 {
444 fServ->Close(option);
445 if (TestBit(kIsOwner))
446 {
447 delete fServ;
448 fServ=0;
449 ResetBit(kIsOwner);
450 fType=kIsZombie;
451 }
452 }
453}
454
455// --------------------------------------------------------------------------
456//
457// Send a SQL query to the SQL server.
458//
459// If MSQLServer is no server (column, row, ...) NULL is returned and an
460// error message is send to stdout.
461//
462// If the query failed for some reason an error message is send to stdout
463// and NULL is returned.
464//
465// If everything works fine a TSQLResult is returned. Make sure that you
466// delete it!
467//
468TSQLResult *MSQLServer::Query(const char *sql) /*FOLD00*/
469{
470 if (!fServ)
471 return NULL;
472
473 if (fType!=kIsServer)
474 {
475 cout << "ERROR: MSQLServer::Query - this is not a server!" << endl;
476 return NULL;
477 }
478
479 TSQLResult *res = fServ->Query(sql);
480 if (!res)
481 {
482 cout << /*"ERROR: MSQLServer::Query - Query failed: " <<*/ sql << endl;
483 return NULL;
484 }
485
486 return res;
487}
488
489// --------------------------------------------------------------------------
490//
491// Send a SQL query to the SQL server.
492//
493// If MSQLServer is no server (column, row, ...) NULL is returned and an
494// error message is send to stdout.
495//
496// If the query failed kFALSE is returned.
497//
498// On success kTRUE is returned.
499//
500Bool_t MSQLServer::Exec(const char* sql)
501{
502 if (!fServ)
503 return kFALSE;
504
505#if ROOT_VERSION_CODE < ROOT_VERSION(5,12,00)
506 TSQLResult *res = fServ->Query(sql);
507 if (!res)
508 {
509 cout << "ERROR: MSQLServer::Exec - Query failed: " << sql << endl;
510 return kFALSE;
511 }
512 delete res;
513 return kTRUE;
514#else
515 if (fType!=kIsServer)
516 {
517 cout << "ERROR: MSQLServer::Exec - this is not a server!" << endl;
518 return kFALSE;
519 }
520
521 return fServ->Exec(sql);
522#endif
523}
524
525Int_t MSQLServer::SelectDataBase(const char *dbname) /*FOLD00*/
526{
527 fDataBase = dbname;
528 return fType==kIsServer && fServ ? fServ->SelectDataBase(dbname) : 0;
529}
530
531TSQLResult *MSQLServer::GetDataBases(const char *wild) /*FOLD00*/
532{
533 return fType==kIsServer && fServ ? fServ->GetDataBases(wild) : NULL;
534}
535
536TSQLResult *MSQLServer::GetTables(const char *wild, const char *dbname) /*FOLD00*/
537{
538 return fType==kIsServer && fServ ? fServ->GetTables(dbname?dbname:fDataBase.Data(), wild) : NULL;
539}
540
541TSQLResult *MSQLServer::GetColumns(const char *table, const char *wild, const char *dbname) /*FOLD00*/
542{
543 return fType==kIsServer && fServ ? fServ->GetColumns(dbname?dbname:fDataBase.Data(), table, wild) : NULL;
544}
545
546Int_t MSQLServer::CreateDataBase(const char *dbname) /*FOLD00*/
547{
548 return fType==kIsServer && fServ ? fServ->CreateDataBase(dbname) : 0;
549}
550
551Int_t MSQLServer::DropDataBase(const char *dbname) /*FOLD00*/
552{
553 return fType==kIsServer && fServ ? fServ->DropDataBase(dbname) : 0;
554}
555
556Int_t MSQLServer::Reload() /*FOLD00*/
557{
558 return fType==kIsServer && fServ ? fServ->Reload() : 0;
559}
560
561Int_t MSQLServer::Shutdown() /*FOLD00*/
562{
563 return fType==kIsServer && fServ ? fServ->Shutdown() : 0;
564}
565
566const char *MSQLServer::ServerInfo() /*FOLD00*/
567{
568 return fType==kIsServer && fServ ? fServ->ServerInfo() : "";
569}
570
571Bool_t MSQLServer::IsConnected() const
572{
573 return fType==kIsServer && fServ ? fServ->IsConnected() : kFALSE;
574}
575
576const char *MSQLServer::GetName() const
577{
578 if (!fServ)
579 return "Unconnected!";
580
581 switch (fType)
582 {
583 case kIsServer: return Form("%s://%s:%d/%s", fServ->GetDBMS(), fServ->GetHost(), fServ->GetPort(), fDataBase.Data());
584 case kIsDataBase: return GetNameDataBase();
585 case kIsTable: return GetNameTable();
586 case kIsColumn: return GetNameColumn();
587 default: return "n/a";
588 }
589}
590
591Bool_t MSQLServer::Split(TString &url, TString &user, TString &pasw) const
592{
593 const Ssiz_t pos1 = url.First("://")+3;
594 const Ssiz_t pos2 = url.Last(':') +1;
595 const Ssiz_t pos3 = url.First('@');
596
597 if (pos1<0 || pos2<0 || pos3<0 || pos1>pos2 || pos2>pos3)
598 return kFALSE;
599
600 user = url(pos1, pos2-pos1-1);
601 pasw = url(pos2, pos3-pos2);
602
603 url.Remove(pos1, pos3+1-pos1);
604
605 return kTRUE;
606}
607
608void MSQLServer::Init(const char *connection, const char *user, const char *password) /*FOLD00*/
609{
610 fType = kIsZombie;
611
612 fServ = TSQLServer::Connect(connection, user, password);
613 if (fServ)
614 {
615 gROOT->GetListOfBrowsables()->Add(this, connection);
616 fType = kIsServer;
617 SetBit(kIsOwner);
618 SetBit(kMustCleanup);
619 }
620 else
621 fType = kIsZombie;
622
623 fList.SetOwner();
624}
625
626void MSQLServer::InitEnv(TEnv &env, const char *prefix)
627{
628 TString url = env.GetValue("URL", "");
629 TString db = env.GetValue("Database", "");
630 TString user = env.GetValue("User", "");
631 TString pass = env.GetValue("Password", "");
632
633 user = env.GetValue(Form("%s.User", db.Data()), user);
634
635 pass = env.GetValue(Form("%s.Password", user.Data()), pass);
636 pass = env.GetValue(Form("%s.%s.Password", db.Data(), user.Data()), pass);
637
638 if (prefix)
639 {
640 url = env.GetValue(Form("%s.URL", prefix), url);
641 db = env.GetValue(Form("%s.Database", prefix), db);
642
643 user = env.GetValue(Form("%s.User", prefix), user);
644 user = env.GetValue(Form("%s.%s.User", prefix, db.Data()), user);
645
646 pass = env.GetValue(Form("%s.Password", prefix), pass);
647 pass = env.GetValue(Form("%s.%s.Password", prefix, user.Data()), pass);
648 pass = env.GetValue(Form("%s.%s.%s.Password", prefix, db.Data(), user.Data()), pass);
649 }
650
651 user = user.Strip(TString::kBoth);
652 pass = pass.Strip(TString::kBoth);
653 url = url.Strip(TString::kBoth);
654
655 if (user.IsNull() && pass.IsNull())
656 {
657 if (!Split(url, user, pass))
658 {
659 fType = kIsZombie;
660 return;
661 }
662 }
663
664 Init(url, user, pass);
665
666 if (IsConnected() && !db.IsNull())
667 SelectDataBase(db);
668}
669
670MSQLServer::MSQLServer(const char *connection, const char *user, const char *password) /*FOLD00*/
671{
672 Init(connection, user, password);
673}
674
675// --------------------------------------------------------------------------
676//
677// Instantiate a dabase connection either by
678// mysql://user:password@url/database
679// or by a resource file (in teh given string doesn't contain mysql://)
680//
681MSQLServer::MSQLServer(const char *u) : fType(kIsZombie) /*FOLD00*/
682{
683 if (TString(u).Contains("mysql://", TString::kIgnoreCase))
684 {
685 TString url(u);
686 TString user, pasw;
687
688 if (!Split(url, user, pasw))
689 {
690 fType = kIsZombie;
691 return;
692 }
693 Init(url, user, pasw);
694 }
695 else
696 {
697 TEnv env(u);
698 InitEnv(env);
699 }
700}
701
702MSQLServer::MSQLServer(TEnv &env, const char *prefix)
703{
704 InitEnv(env, prefix);
705}
706
707MSQLServer::MSQLServer()
708{
709 if (gEnv)
710 InitEnv(*gEnv);
711}
712
713MSQLServer::MSQLServer(MSQLServer &serv)
714{
715 fServ = serv.fServ;
716
717 fDataBase = serv.fDataBase;
718 fTable = serv.fTable;
719 fColumn = serv.fColumn;
720
721 fType = serv.fType;
722}
723
724MSQLServer::~MSQLServer() /*FOLD00*/
725{
726 if (gDebug>0)
727 cout << "Delete: " << GetName() << endl;
728 Close();
729}
730
731Bool_t MSQLServer::PrintError(const char *txt, const char *q) const /*FOLD00*/
732{
733 cout << "Fatal error acessing database: " << txt << endl;
734 cout << "Query: " << q << endl;
735 return kFALSE;
736}
737
738TString MSQLServer::GetEntry(const char *where, const char *col, const char *table) const /*FOLD00*/
739{
740 if (!fServ)
741 return "";
742
743 if (table==0)
744 table = Form("%s.%s", (const char *)fDataBase, (const char*)fTable);
745 if (col==0)
746 col = (const char *)fColumn;
747
748 const TString query(Form("SELECT %s FROM %s WHERE %s", col, table, where));
749
750 TSQLResult *res = fServ->Query(query);
751 if (!res)
752 return (PrintError("GetEntry - TSQLResult==NULL", query), "");
753
754 if (res->GetFieldCount()!=1)
755 {
756 delete res;
757 return (PrintError("GetEntry - Number of columns != 1", query), "");
758 }
759
760 if (res->GetRowCount()>1)
761 {
762 delete res;
763 return (PrintError("GetEntry - Number of rows > 1", query), "");
764 }
765
766 if (res->GetRowCount()==0)
767 {
768 delete res;
769 return "";
770 }
771
772 const char *fld = res->Next()->GetField(0);
773 if (!fld)
774 {
775 delete res;
776 return (PrintError("GetEntry - Entry is empty", query), "");
777 }
778
779 const TString rc(fld);
780 delete res;
781 return rc;
782}
783
784// --------------------------------------------------------------------------
785//
786// Return the name of the (first) column with a primary key
787//
788TString MSQLServer::GetPrimaryKeys(const char *table)
789{
790 TSQLResult *res = GetColumns(table);
791 if (!res)
792 return "";
793
794 TObjArray arr;
795 arr.SetOwner();
796
797 TSQLRow *row = 0;
798 while ((row=res->Next()))
799 {
800 const TString key = (*row)[3];
801 if (key=="PRI")
802 arr.Add(new TObjString((*row)[0]));
803 delete row;
804 }
805 delete res;
806
807 arr.Sort();
808
809 TString rc;
810 for (int i=0; i<arr.GetEntries(); i++)
811 {
812 if (i>0)
813 rc += ", ";
814 rc += arr[i]->GetName();
815 }
816 return rc;
817}
818
819// --------------------------------------------------------------------------
820//
821// Searches in the text for patterns like "Table.Column". If such a pettern
822// is found the primary key of the table is requested a "LEFT JOIN"
823// with this Table is added ON the identity of the primary key of Table
824// with the given table.
825//
826TString MSQLServer::GetJoins(const char *table, const TString text)
827{
828 Int_t p=0;
829
830 TString mods;
831 TArrayI pos;
832
833 // Find all Table.Column expression. Because also floating point
834 // numbers can contain a dot the result has to be checked carefully
835 TString joins;
836 TPRegexp reg = TPRegexp("\\w+[.]\\w+");
837 while (1)
838 {
839 // Check whether expression is found
840 if (reg.Match(text, mods, p, 130, &pos)==0)
841 break;
842
843 // Get expression from text
844 const TString expr = text(pos[0], pos[1]-pos[0]);
845 p = pos[1];
846
847 if (expr.IsFloat())
848 continue;
849
850 const TString tab = expr(0, expr.First('.'));
851 //const TString var = expr(expr.First('.')+1, expr.Length());
852
853 // If the table found is the primary table itself skip it.
854 if (tab==table)
855 continue;
856
857 // If this join has already be set, skip it.
858 if (joins.Contains(Form(" %s ", tab.Data())))
859 continue;
860
861 // Now get the primary key of the table to be joined
862 const TString prim = GetPrimaryKeys(tab);
863 if (prim.IsNull())
864 continue;
865
866 joins += Form("LEFT JOIN %s USING (%s) ", tab.Data(), prim.Data());
867 }
868
869 if (!joins.IsNull())
870 joins += " ";
871
872 return joins;
873}
874
875void MSQLServer::RecursiveRemove(TObject *obj)
876{
877 if (fServ==obj)
878 {
879 fServ=NULL;
880 fType = kIsZombie;
881 ResetBit(kIsOwner);
882 }
883}
Note: See TracBrowser for help on using the repository browser.