home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 22 gnu / 22-gnu.zip / db02_src.zip / dbase.cc < prev    next >
C/C++ Source or Header  |  1994-02-23  |  18KB  |  676 lines

  1. /**************************************************************************
  2.  * Source Id :
  3.  *
  4.  * $Id: dbase.cc,v 1.46 1993/11/04 14:57:43 kevinl Exp $
  5.  *-------------------------------------------------------------------------
  6.  * Project Notes :
  7.  *
  8.  *  Diamond Base
  9.  *  ============
  10.  *    A solid database implementation, spurred on by the continuing
  11.  *  Metal (Lead) Base saga.
  12.  *
  13.  *  Project Team :
  14.  *      A. Davison
  15.  *      K. Lentin
  16.  *      D. Platt
  17.  *
  18.  *    Project Commenced : 05-02-1993
  19.  *
  20.  *-------------------------------------------------------------------------
  21.  *  Module Notes :
  22.  *
  23.  *        Diamond Base Object : this object is the main database which will
  24.  *    interact with client code via a transaction protocol
  25.  *
  26.  *
  27.  *
  28.  *  Original Author : Andy
  29.  *
  30.  *-------------------------------------------------------------------------
  31.  * Revision History:
  32.  *
  33.  * $Log: dbase.cc,v $
  34. // Revision 1.46  1993/11/04  14:57:43    kevinl
  35. // NumNames becomes nsOpen
  36. //
  37. // Revision 1.45  1993/10/13  13:14:36    kevinl
  38. // Fixed get
  39. //
  40. // Revision 1.44  1993/10/05  07:29:41    kevinl
  41. // Attach now handles dbObjData
  42. //
  43. // Revision 1.43  1993/07/19  11:43:14    kevinl
  44. // Removed all the pesky ifdef'ed break's
  45. //
  46. // Revision 1.42  1993/07/11  10:49:21    kevinl
  47. // dbString version
  48. //
  49. // Revision 1.41  1993/07/11  08:18:12    kevinl
  50. // Stopped the NameServer exiting when the config file is empty/missing
  51. //
  52. // Revision 1.40  1993/06/23  05:21:22    kevinl
  53. // Mallocs are now in angular brackets
  54. //
  55. // Revision 1.39  1993/06/20  13:43:05    kevinl
  56. // Fixed multiple mallocs
  57. //
  58. // Revision 1.38  1993/05/26  01:01:39    kevinl
  59. // MALLOC_H_MISSING
  60. //
  61. // Revision 1.37  1993/05/12  08:16:26    kevinl
  62. // detaxch now kills a query
  63. //
  64. // Revision 1.36  1993/05/12  06:34:12    kevinl
  65. // ti_put now checks for nfound not dup!
  66. //
  67. // Revision 1.35  1993/05/11  14:51:30    kevinl
  68. // Oops, minor spelling error
  69. //
  70. // Revision 1.34  1993/05/11  14:42:54    kevinl
  71. // Version numbers
  72. // Extract does not return anything if not found
  73. //
  74. // Revision 1.33  1993/05/07  13:55:06    kevinl
  75. // Fixed put
  76. //
  77. // Revision 1.32  1993/05/06  04:00:38    kevinl
  78. // SASC define for malloc.h and some extra breaks
  79. //
  80. // Revision 1.31  1993/05/03  01:33:03    kevinl
  81. // Cosmetic (mainly) changes for CC
  82. //
  83. // Revision 1.30  1993/05/01  14:37:13    kevinl
  84. // Got rid of ints
  85. //
  86. // Revision 1.29  1993/04/27  07:14:13    kevinl
  87. // Added put and write
  88. //
  89. // Revision 1.28  1993/04/25  11:06:40    kevinl
  90. // Fixed up some error reporting that was a little out
  91. // Comments
  92. //
  93. // Revision 1.27  1993/04/15  07:58:37    kevinl
  94. // Added ti_del
  95. //
  96. // Revision 1.26  1993/04/15  04:21:52    kevinl
  97. // Moved malloc.h
  98. //
  99. // Revision 1.25  1993/04/11  10:25:16    kevinl
  100. // Fixed up return codes from find
  101. //
  102. // Revision 1.24  1993/04/11  05:49:02    kevinl
  103. // Rationalised find/seek/peek methods etc
  104. //
  105. // Revision 1.23  1993/04/09  13:00:29    kevinl
  106. // Stats can be called from diaRel now.
  107. //
  108. // Revision 1.22  1993/04/08  05:26:40    kevinl
  109. // FGixed some memory leaks
  110. //
  111. // Revision 1.21  1993/04/08  00:28:41    kevinl
  112. // Some range checking on queryId's
  113. //
  114. // Revision 1.20  1993/04/07  07:59:58    kevinl
  115. // qBegin now qEnd's a query if it's active
  116. //
  117. // Revision 1.19  1993/04/07  02:29:21    kevinl
  118. // Changed qEnd to reset qId to -1
  119. //
  120. // Revision 1.18  1993/04/06  04:42:19    kevinl
  121. // Check queryId in trans before passing on...
  122. //
  123. // Revision 1.17  1993/04/06  04:27:44    kevinl
  124. // Fixed trans so qBegin gets correct reference
  125. //
  126. // Revision 1.16  1993/04/06  01:01:55    kevinl
  127. // Put a string in db_toomany
  128. // fixed get transaction
  129. //
  130. // Revision 1.15  1993/04/05  01:06:12    kevinl
  131. // Assignment of dbObj now to correct spot
  132. //
  133. // Revision 1.14  1993/04/05  01:02:49    kevinl
  134. // Incorrect comparison for newDbNum fixed
  135. //
  136. // Revision 1.13  1993/04/05  00:52:39    kevinl
  137. // Checking correct field in attach now for empty slot
  138. //
  139. // Revision 1.12  1993/04/05  00:42:17    kevinl
  140. // replaced << with < in attach
  141. // put in a break in attach
  142. //
  143. // Revision 1.11  1993/04/04  23:57:09    kevinl
  144. // Now cache the dbObj's so they don't duisappear
  145. // Made trans much neater by removing all the ugly indirection
  146. //
  147. // Revision 1.10  1993/04/01  04:23:46    kevinl
  148. // Modified/added transactions
  149. // Now have get, extract, and find
  150. //
  151. // Revision 1.9  1993/03/30  07:13:29  davison
  152. // Um.
  153. //
  154. // Revision 1.8  1993/03/30  03:28:01  davison
  155. // Fixed multiple delete problem.
  156. //
  157. // Revision 1.7  1993/03/29  08:04:43  darrenp
  158. // Fixed multiple deletions of other peoples' data
  159. // Removed bogus error reporting
  160. // Debug malloc library
  161. //
  162. // Revision 1.6  1993/03/28  10:37:55  kevinl
  163. // Modified for dbErr
  164. //
  165. // Revision 1.5  1993/03/28  04:53:59  root
  166. // more error code standardization.
  167. //
  168. // Revision 1.4  1993/03/26  06:15:57  darrenp
  169. // standardised error codes.
  170. //
  171. // Revision 1.3  1993/03/26  05:49:24  darrenp
  172. // Small syntax errors.
  173. //
  174. // Revision 1.2  1993/03/25  23:25:16  davison
  175. // Added a few more transactions
  176. // Added the detach method
  177. //
  178. // Revision 1.1  1993/03/25  22:28:50  davison
  179. // Initial revision
  180. //
  181.  **************************************************************************/
  182.  
  183. #ifdef __EMX__
  184. #include <strstrea.h>
  185. #else
  186. #include <strstream.h>
  187. #endif
  188. #if !defined(MALLOC_H_MISSING) && !defined(MALLOC_H_INCLUDED)
  189. extern "C" {
  190. #include <malloc.h>
  191. }
  192. #define MALLOC_H_INCLUDED
  193. #endif
  194. #include <dbase.h>
  195. #include <nserver.h>
  196. #include <dbobj.h>
  197. #include <btree.h>
  198. #include <diarel.h>
  199. #include <version.h>
  200.  
  201. //const int MAX_REG = 100;
  202.  
  203. char*
  204. diamondBase::verStr(void)
  205. {
  206.     return "$Id: dbase.cc,v 1.46 1993/11/04 14:57:43 kevinl Exp $";
  207. }
  208.  
  209. //----------------------------------------------------
  210. // Diamond Base constructor
  211. //
  212. //
  213. //    Provide this with the name of the database description
  214. // file.
  215.  
  216. diamondBase::diamondBase(char* name):TNameServer(name)
  217. {
  218.     // Startup the database.
  219.     // Make all the arrays point to nothing.
  220.  
  221.     for(int i=0;i<MAX_REG;i++)
  222.         regInfo[i] = 0;
  223.     for (i=0; i<MAX_DB_INFO; i++)
  224.         dbList[i].theDbObject = 0;
  225. }
  226.  
  227.  
  228. //---------------------------------------
  229. //  Attach a user class to the
  230. // database server.
  231.  
  232. dbError diamondBase::attach(const char* name, object* resBuffer, long& refId,
  233. dbObjData* & objData)
  234. {
  235.  
  236.     long    nameId;         // The ID of the name, returned by the nameserver.
  237.     char*    path;            // The path for the relation.
  238.     char*    prefix;
  239.     int        cpyDbNum=-1;    // The dbList number of a user class which
  240.                 // already is the requested relation.
  241.     int        newRegNum=-1;    // The registration number for the new user class.
  242.     int        newDbNum=-1;    // New spot in the dbList
  243.     int        lastDbNum=-1;    // Oldest unused dbObj
  244.  
  245.     // Is the database alive
  246.  
  247.     if (!nsOpen)
  248.         return dbErr(db_notopen, "Database config file missing or empty");
  249.  
  250.     // First, is this relation already open ?
  251.  
  252.     path = queryName(name,nameId);
  253.     prefix = new char[strlen(name)+1];
  254.     strcpy(prefix,name);
  255.  
  256.     // Find a blank registration spot
  257.     for (int i=0; i < MAX_REG; i++)
  258.         if (!regInfo[i])
  259.         {
  260.             newRegNum = i;
  261.             break;
  262.         }
  263.  
  264.     // We couldn't find one. Invalidate refId and return an error.
  265.  
  266.     if (newRegNum == -1)
  267.     {
  268.         refId = newRegNum;
  269.         return dbErr(db_toomany, "attachments");
  270.     }
  271.  
  272.     // Search all the dbObj classes attached
  273.     time_t lastTime = (time_t) -1;
  274.     for(i=0;i<MAX_DB_INFO;i++)
  275.     {
  276.         // There is an object there
  277.         if (dbList[i].theDbObject)
  278.         {
  279.             // Is it the one we want? cpyDbNum says which one to copy
  280.             if (dbList[i].nameId == nameId) {
  281.                 cpyDbNum = i;    // Found a reference to this relation.
  282.                                 // Remember it.
  283.             }
  284.  
  285.             // See if this is the oldest detached dbObj
  286.             if (dbList[i].detachTime && (lastTime == -1 || dbList[i].detachTime < lastTime))
  287.             {
  288.                 // This is a detached dbObj which is older than any other
  289.                 lastTime = dbList[i].detachTime;
  290.                 lastDbNum = i;
  291.             }
  292.         }
  293.         else
  294.             if (newDbNum == -1)
  295.                 newDbNum = i; // Remember the location of the first empty slot.
  296.     }
  297.  
  298.     // Now:
  299.     // cpyDbNum holds the spot (if any) where this dbObj is already present
  300.     // lastDbNum holds the spot of the oldest detached dbObj
  301.     // newDbNum holds the spot of a free slot
  302.  
  303.     if (cpyDbNum == -1)    // Couldn't find an instance of this relation
  304.     {
  305.         // Ok. First we need to create a dbObject for this relation.
  306.         dbObject* dbObj = new dbObject(path,prefix);
  307.  
  308.         if (newDbNum != -1) // Got a spare spot?
  309.             cpyDbNum = newDbNum; // Put it here
  310.         else
  311.         {
  312.             // No spare spot, nuke an old one.
  313.             // NB refCount will be 0 otherwise no detach time
  314.             // We delete the oldest detached one
  315.  
  316.             delete dbList[lastDbNum].theDbObject;
  317.             cpyDbNum = lastDbNum; // And put the new one here
  318.         }
  319.         // Now cpyDbNum points where to put it, so store it
  320.         dbList[cpyDbNum].theDbObject = dbObj;
  321.     }
  322.  
  323.     // We create a new regInfo for each registration, whether the dbObj was
  324.     // there or not.
  325.     regInfo[newRegNum] = new dbRegInfo(resBuffer,
  326.                                        cpyDbNum);
  327.  
  328.     // Now there is one more reference
  329.     dbList[cpyDbNum].theDbObject->usageCount++;
  330.     objData = dbList[cpyDbNum].theDbObject->getObjData();
  331.  
  332.     // It hasn't been detached.
  333.     dbList[cpyDbNum].detachTime = 0;
  334.  
  335.     // And who it is.
  336.     dbList[cpyDbNum].nameId = nameId;
  337.  
  338.     // Clean up.
  339.     delete path;
  340.     delete prefix;
  341.     refId = newRegNum;    // return the registration number for this user class.
  342.     return dbErr(db_ok);
  343. }
  344.  
  345. // The destructor. Makes sure all memory is cleaned up
  346.  
  347. diamondBase::~diamondBase()
  348. {
  349.     for(int i=0;i<MAX_REG;i++)
  350.     {
  351.         delete regInfo[i];
  352.         regInfo[i]=0;
  353.     }
  354.     for (i=0; i<MAX_DB_INFO; i++)
  355.         delete dbList[i].theDbObject;
  356. }
  357.  
  358. // Return the current diamondBase version string
  359.  
  360. char* diamondBase::version(bool full)
  361. {
  362.     strstream s;
  363.     s << "DiamondBase Version " << VERSION_MAJOR << "." << VERSION_MINOR
  364.       << "." << PATCH_LEVEL << PATCH_STATUS << "  Released: "
  365.       << RELEASE_DATE << endl;
  366.  
  367.     if (full)
  368.     {
  369.         s << "diamondBase: " << verStr() << endl;
  370.         s << "TNameServer: " << TNameServer::verStr() << endl;
  371.         bTree b;
  372.         s << "bTree: " << b.verStr() << endl;
  373.         s << "recServer: " << b.recServer::verStr() << endl;
  374.         s << "cache: " << b.cache::verStr() << endl;
  375.         // diaRel can't be instantiated.
  376.         s << "diaRel: " << diaVerStr() << endl;
  377.         bucket bu(0,0,0,LEAF,0,0,0);
  378.         s << "bucket: " << bu.verStr() << endl;
  379.         dbRegInfo ri(0,0);
  380.         s << "dbRegInfo: " << ri.verStr() << endl;
  381.         dbQueryInfo qi(0,0);
  382.         s << "dbQueryInfo: " << qi.verStr() << endl;
  383.         dbObject dbo;
  384.         s << "dbObject: " << dbo.verStr() << endl;
  385.         dbString str;
  386.         s << "dbString: " << str.verStr() << endl;
  387.     }
  388.     return s.str();
  389. }
  390.  
  391. //--------------------------------------------
  392. // Perform some transaction, given by the
  393. // id argument, with the dbObject defined
  394. // in the regInfo structure.
  395.  
  396. #ifdef __CC
  397. dbError diamondBase::trans(long refId, transId id, long idx)
  398. #else
  399. dbError diamondBase::trans(long refId, diamondBase::transId id, long idx)
  400. #endif
  401. {
  402.     // Check the refId is valid
  403.     if (refId < 0 || refId > MAX_REG)
  404.         return dbErr(db_range);
  405.  
  406.     // Make sure it's in use. Don't use db_nfound 'cos the transaction
  407.     // itself may. Don't want to confuse anybody.
  408.     if (!regInfo[refId])
  409.         return dbErr(db_range);
  410.  
  411.     // These 3 just make the code below look much better.
  412.     // Note than in some cases below, this copy of queryId is not used,
  413.     // but the original is. This is because the action concerned modifies
  414.     // it.
  415.     object* theObj = regInfo[refId]->buffer;
  416.     dbObject* theDbObject = dbList[regInfo[refId]->dbListId].theDbObject;
  417.     long queryId = regInfo[refId]->queryId;
  418.  
  419.     // For most situations, not having a query is an error.
  420.     if (queryId == -1)
  421.         switch(id)
  422.         {
  423.             case ti_first:
  424.             case ti_seekFirst:
  425.             case ti_end:
  426.             case ti_next:
  427.             case ti_peekNext:
  428.             case ti_prev:
  429.             case ti_peekPrev:
  430.             case ti_last:
  431.             case ti_seekLast:
  432.             case ti_find:
  433.             case ti_seek:
  434.             case ti_extract:
  435.             case ti_write:
  436.                 return dbErr(db_noquery);
  437.             default:
  438.                 break;
  439.         }
  440.  
  441.     // One for each transaction type. Most are self explanatory, see dbObj
  442.     switch(id)
  443.     {
  444.         case ti_add:
  445.             return theDbObject->add(*theObj);
  446.  
  447.         case ti_del:
  448.             return theDbObject->del(*theObj);
  449.  
  450.         // A first is merely a seekFirst followed by a next
  451.         case ti_seekFirst:
  452.         case ti_first:
  453.         {
  454.             dbError err = theDbObject->qSeekFirst(queryId, *theObj);
  455.             if (id == ti_seekFirst)
  456.                 return err;
  457.             if (err == db_ok)
  458.                 err = theDbObject->qNext(queryId, *theObj);
  459.             return err;
  460.         }
  461.  
  462.         // Start a new query
  463.         case ti_begin:
  464.         {
  465.             if (queryId != -1)
  466.             {
  467.                 // If we already have a query, end it now
  468.                 dbError err;
  469.                 // This queryId not removed 'cos it's a reference
  470.                 err = theDbObject->qEnd(regInfo[refId]->queryId);
  471.                 if (err != db_ok || err != db_noquery)
  472.                     return err;
  473.             }
  474.             // This queryId not removed 'cos it's a reference
  475.             return theDbObject->qBegin(idx, regInfo[refId]->queryId);
  476.         }
  477.         case ti_write:
  478.             return theDbObject->qWrite(queryId, *theObj);
  479.  
  480.         case ti_end:
  481.             // This queryId not removed 'cos it's a reference
  482.             return theDbObject->qEnd(regInfo[refId]->queryId);
  483.  
  484.         case ti_peekNext:
  485.             return theDbObject->qPeekNext(queryId, *theObj);
  486.  
  487.         case ti_next:
  488.             return theDbObject->qNext(queryId, *theObj);
  489.  
  490.         case ti_peekPrev:
  491.             return theDbObject->qPeekPrev(queryId, *theObj);
  492.  
  493.         case ti_prev:
  494.             return theDbObject->qPrev(queryId, *theObj);
  495.  
  496.         case ti_seekLast:
  497.         case ti_last:
  498.         {
  499.             dbError err = theDbObject->qSeekLast(queryId, *theObj);
  500.             if (id == ti_seekLast)
  501.                 return err;
  502.             if (err == db_ok)
  503.                 err = theDbObject->qPrev(queryId, *theObj);
  504.             return err;
  505.         }
  506.  
  507.         // All these are the same. A find/extract is a seek then a next
  508.         // find/extract differ only in whether a lock is set
  509.         case ti_seek:
  510.         case ti_find:
  511.         case ti_extract:
  512.         {
  513.             dbError err1 = theDbObject->qSeek(queryId, *theObj);
  514.             if (id == ti_seek || (id==ti_extract && err1 == db_nfound))
  515.                 return dbErr(err1);
  516.             dbError err2 = theDbObject->qNext(queryId, *theObj, (bool)(id==ti_extract));
  517.             if (err1 == db_ok)
  518.                 return err2;
  519.             return err1;
  520.         }
  521.  
  522.         // get creates a query, does a find and then removes the query
  523.         case ti_get:
  524.         {
  525.             long tempQid;
  526.             dbError err;
  527.  
  528.             // Start a query
  529.             err = theDbObject->qBegin(idx, tempQid);
  530.  
  531.             // If it failed bail out
  532.             if (err != db_ok)
  533.                 return err;
  534.  
  535.             // Search for the object.
  536.             err = theDbObject->qSeek(tempQid, *theObj);
  537.  
  538.             // If that fails (including not found), remove the query and go
  539.             if (err != db_ok)
  540.             {
  541.                 theDbObject->qEnd(tempQid);
  542.                 if (err == db_eof)
  543.                     return dbError(db_nfound);
  544.                 else
  545.                     return err;
  546.             }
  547.  
  548.             // Otherwise take the one we want
  549.             err = theDbObject->qNext(tempQid, *theObj, false);
  550.  
  551.             // If that fails, remove the query and go
  552.             if (err != db_ok)
  553.             {
  554.                 theDbObject->qEnd(tempQid);
  555.                 if (err == db_eof)
  556.                     return dbError(db_nfound);
  557.                 else
  558.                     return err;
  559.             }
  560.  
  561.             // Otherwise end the query and return. NB this is not combined
  562.             // with the above call to qEnd because of the different treatment
  563.             // of the returned error. In the above call, it is ignored.
  564.             return theDbObject->qEnd(tempQid);
  565.         }
  566.  
  567.         // put creates a query, does a write and then removes the query
  568.         case ti_put:
  569.         {
  570.             long tempQid;
  571.             dbError err;
  572.  
  573.             // Start a query
  574.             err = theDbObject->qBegin(idx, tempQid);
  575.  
  576.             // If it failed bail out
  577.             if (err != db_ok)
  578.                 return err;
  579.  
  580.             // Otherwise write the data
  581.             err = theDbObject->qWrite(tempQid, *theObj);
  582.  
  583.             if (err == db_nfound)
  584.             {
  585.                 err = theDbObject->add(*theObj);
  586.             }
  587.  
  588.             // If that fails, remove the query and go
  589.             if (err != db_ok)
  590.             {
  591.                 theDbObject->qEnd(tempQid);
  592.                 return err;
  593.             }
  594.  
  595.             // Otherwise end the query and return. NB this is not combined
  596.             // with the above call to qEnd because of the different treatment
  597.             // of the returned error. In the above call, it is ignored.
  598.             return theDbObject->qEnd(tempQid);
  599.         }
  600.  
  601.         case ti_stats:
  602.             theDbObject->stats();
  603.             break;
  604.  
  605.         default:
  606.             return dbErr(db_unimp);
  607.     }
  608.  
  609.     return dbErr(db_ok);
  610. }
  611.  
  612. //-----------------------------------------------------
  613. // Detach a user class from the database server.
  614. //
  615. // When detaching a user class, care must be taken
  616. // not to delete the dbObject associated with the
  617. // user class if another user class has a handle on it.
  618. //
  619. // To prevent this the registration space is searched
  620. // for references to the dbObject in question. If one
  621. // is found, the regInfo::saveDbObject() method is called,
  622. // preventing the dbObject from being deleted when the
  623. // regInfo destructor is invoked.
  624. // [KEV: See below]
  625.  
  626. dbError diamondBase::detach(long refId)
  627. {
  628. // Kev: I have deleted all this. It's saving the wrong reginfo
  629. // plus since the refcount, we don't need it any more.
  630. #if 0
  631.     // Ok. First figure out whether or not the relation is in use
  632.     // by another user class.
  633.  
  634.     for(int i=0;i<MAX_REG;i++)
  635.     {
  636.     if ((regInfo[i])&&(i!= refId))    // Don't look at nothing, nor
  637.                     // ourselves :-)
  638.         if (regInfo[i]->nameId == regInfo[refId]->nameId)
  639.         regInfo[i]->saveDbObject();
  640.  
  641.     }
  642. #endif
  643.  
  644.     // Make sure the reference is valid
  645.     if (refId < 0 || refId > MAX_REG)
  646.         return dbErr(db_range);
  647.  
  648.     if (!regInfo[refId])
  649.         return dbErr(db_range);
  650.  
  651.     // First sort out the dbList
  652.     // Decrease the reference count of the dbObject associated with the
  653.     // regInfo in question
  654.     if (!(--dbList[regInfo[refId]->dbListId].theDbObject->usageCount))
  655.     {
  656.         // Nobody else is using this. set a detach time.
  657.         dbList[regInfo[refId]->dbListId].detachTime = time(0);
  658.  
  659.         // Don't delete anything, leave it hanging around.
  660.         // It will be deleted in attach if its spot is needed.
  661.         // We leave it here to save time when the next one is attached
  662.         // since the usage pattern is often to delete an object then create a
  663.         // new one.
  664.     }
  665.     // Now delete the regInfo structure for this user class.
  666.  
  667.     dbList[regInfo[refId]->dbListId].theDbObject->qEnd(regInfo[refId]->queryId);
  668.     delete regInfo[refId];
  669.  
  670.     // And finally set it to zero to signify an empty slot.
  671.  
  672.     regInfo[refId] = 0;
  673.  
  674.     return dbErr(db_ok);
  675. }
  676.