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

Last change on this file since 10398 was 10166, checked in by tbretz, 14 years ago
Removed the old obsolete cvs header line.
File size: 20.3 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 if (user.IsNull() && pass.IsNull())
652 {
653 if (!Split(url, user, pass))
654 {
655 fType = kIsZombie;
656 return;
657 }
658 }
659
660 Init(url, user, pass);
661
662 if (IsConnected() && !db.IsNull())
663 SelectDataBase(db);
664}
665
666MSQLServer::MSQLServer(const char *connection, const char *user, const char *password) /*FOLD00*/
667{
668 Init(connection, user, password);
669}
670
671// --------------------------------------------------------------------------
672//
673// Instantiate a dabase connection either by
674// mysql://user:password@url/database
675// or by a resource file (in teh given string doesn't contain mysql://)
676//
677MSQLServer::MSQLServer(const char *u) : fType(kIsZombie) /*FOLD00*/
678{
679 if (TString(u).Contains("mysql://", TString::kIgnoreCase))
680 {
681 TString url(u);
682 TString user, pasw;
683
684 if (!Split(url, user, pasw))
685 {
686 fType = kIsZombie;
687 return;
688 }
689 Init(url, user, pasw);
690 }
691 else
692 {
693 TEnv env(u);
694 InitEnv(env);
695 }
696}
697
698MSQLServer::MSQLServer(TEnv &env, const char *prefix)
699{
700 InitEnv(env, prefix);
701}
702
703MSQLServer::MSQLServer()
704{
705 if (gEnv)
706 InitEnv(*gEnv);
707}
708
709MSQLServer::MSQLServer(MSQLServer &serv)
710{
711 fServ = serv.fServ;
712
713 fDataBase = serv.fDataBase;
714 fTable = serv.fTable;
715 fColumn = serv.fColumn;
716
717 fType = serv.fType;
718}
719
720MSQLServer::~MSQLServer() /*FOLD00*/
721{
722 if (gDebug>0)
723 cout << "Delete: " << GetName() << endl;
724 Close();
725}
726
727Bool_t MSQLServer::PrintError(const char *txt, const char *q) const /*FOLD00*/
728{
729 cout << "Fatal error acessing database: " << txt << endl;
730 cout << "Query: " << q << endl;
731 return kFALSE;
732}
733
734TString MSQLServer::GetEntry(const char *where, const char *col, const char *table) const /*FOLD00*/
735{
736 if (!fServ)
737 return "";
738
739 if (table==0)
740 table = Form("%s.%s", (const char *)fDataBase, (const char*)fTable);
741 if (col==0)
742 col = (const char *)fColumn;
743
744 const TString query(Form("SELECT %s FROM %s WHERE %s", col, table, where));
745
746 TSQLResult *res = fServ->Query(query);
747 if (!res)
748 return (PrintError("GetEntry - TSQLResult==NULL", query), "");
749
750 if (res->GetFieldCount()!=1)
751 {
752 delete res;
753 return (PrintError("GetEntry - Number of columns != 1", query), "");
754 }
755
756 if (res->GetRowCount()>1)
757 {
758 delete res;
759 return (PrintError("GetEntry - Number of rows > 1", query), "");
760 }
761
762 if (res->GetRowCount()==0)
763 {
764 delete res;
765 return "";
766 }
767
768 const char *fld = res->Next()->GetField(0);
769 if (!fld)
770 {
771 delete res;
772 return (PrintError("GetEntry - Entry is empty", query), "");
773 }
774
775 const TString rc(fld);
776 delete res;
777 return rc;
778}
779
780// --------------------------------------------------------------------------
781//
782// Return the name of the (first) column with a primary key
783//
784TString MSQLServer::GetPrimaryKeys(const char *table)
785{
786 TSQLResult *res = GetColumns(table);
787 if (!res)
788 return "";
789
790 TObjArray arr;
791 arr.SetOwner();
792
793 TSQLRow *row = 0;
794 while ((row=res->Next()))
795 {
796 const TString key = (*row)[3];
797 if (key=="PRI")
798 arr.Add(new TObjString((*row)[0]));
799 delete row;
800 }
801 delete res;
802
803 arr.Sort();
804
805 TString rc;
806 for (int i=0; i<arr.GetEntries(); i++)
807 {
808 if (i>0)
809 rc += ", ";
810 rc += arr[i]->GetName();
811 }
812 return rc;
813}
814
815// --------------------------------------------------------------------------
816//
817// Searches in the text for patterns like "Table.Column". If such a pettern
818// is found the primary key of the table is requested a "LEFT JOIN"
819// with this Table is added ON the identity of the primary key of Table
820// with the given table.
821//
822TString MSQLServer::GetJoins(const char *table, const TString text)
823{
824 Int_t p=0;
825
826 TString mods;
827 TArrayI pos;
828
829 // Find all Table.Column expression. Because also floating point
830 // numbers can contain a dot the result has to be checked carefully
831 TString joins;
832 TPRegexp reg = TPRegexp("\\w+[.]\\w+");
833 while (1)
834 {
835 // Check whether expression is found
836 if (reg.Match(text, mods, p, 130, &pos)==0)
837 break;
838
839 // Get expression from text
840 const TString expr = text(pos[0], pos[1]-pos[0]);
841 p = pos[1];
842
843 if (expr.IsFloat())
844 continue;
845
846 const TString tab = expr(0, expr.First('.'));
847 //const TString var = expr(expr.First('.')+1, expr.Length());
848
849 // If the table found is the primary table itself skip it.
850 if (tab==table)
851 continue;
852
853 // If this join has already be set, skip it.
854 if (joins.Contains(Form(" %s ", tab.Data())))
855 continue;
856
857 // Now get the primary key of the table to be joined
858 const TString prim = GetPrimaryKeys(tab);
859 if (prim.IsNull())
860 continue;
861
862 joins += Form("LEFT JOIN %s USING (%s) ", tab.Data(), prim.Data());
863 }
864
865 if (!joins.IsNull())
866 joins += " ";
867
868 return joins;
869}
870
871void MSQLServer::RecursiveRemove(TObject *obj)
872{
873 if (fServ==obj)
874 {
875 fServ=NULL;
876 fType = kIsZombie;
877 ResetBit(kIsOwner);
878 }
879}
Note: See TracBrowser for help on using the repository browser.