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

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