connection.cpp
上传用户:yhdzpy8989
上传日期:2007-06-13
资源大小:13604k
文件大小:17k
源码类别:

生物技术

开发平台:

C/C++

  1. /*
  2.  * ===========================================================================
  3.  * PRODUCTION $Log: connection.cpp,v $
  4.  * PRODUCTION Revision 1000.1  2004/06/01 19:21:40  gouriano
  5.  * PRODUCTION PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.5
  6.  * PRODUCTION
  7.  * ===========================================================================
  8.  */
  9. /* $Id: connection.cpp,v 1000.1 2004/06/01 19:21:40 gouriano Exp $
  10.  * ===========================================================================
  11.  *
  12.  *                            PUBLIC DOMAIN NOTICE
  13.  *               National Center for Biotechnology Information
  14.  *
  15.  *  This software/database is a "United States Government Work" under the
  16.  *  terms of the United States Copyright Act.  It was written as part of
  17.  *  the author's official duties as a United States Government employee and
  18.  *  thus cannot be copyrighted.  This software/database is freely available
  19.  *  to the public for use. The National Library of Medicine and the U.S.
  20.  *  Government have not placed any restriction on its use or reproduction.
  21.  *
  22.  *  Although all reasonable efforts have been taken to ensure the accuracy
  23.  *  and reliability of the software and data, the NLM and the U.S.
  24.  *  Government do not and cannot warrant the performance or results that
  25.  *  may be obtained by using this software or data. The NLM and the U.S.
  26.  *  Government disclaim all warranties, express or implied, including
  27.  *  warranties of performance, merchantability or fitness for any particular
  28.  *  purpose.
  29.  *
  30.  *  Please cite the author in any work or product based on this material.
  31.  *
  32.  * ===========================================================================
  33.  *
  34.  * Author:  Vladimir Soussov
  35.  *
  36.  * File Description:  ODBC connection
  37.  *
  38.  */
  39. #include <ncbi_pch.hpp>
  40. #include <dbapi/driver/odbc/interfaces.hpp>
  41. #include <string.h>
  42. #ifdef HAVE_ODBCSS_H
  43. #include <odbcss.h>
  44. #endif
  45. BEGIN_NCBI_SCOPE
  46. static bool ODBC_xSendDataPrepare(SQLHSTMT cmd, CDB_ITDescriptor& descr_in,
  47.                                   SQLINTEGER size, bool is_text, bool logit, 
  48.           SQLPOINTER id, CODBC_Reporter& rep);
  49. static bool ODBC_xSendDataGetId(SQLHSTMT cmd, SQLPOINTER* id, 
  50.                                 CODBC_Reporter& rep);
  51. CODBC_Connection::CODBC_Connection(CODBCContext* cntx, SQLHDBC con,
  52.                                    bool reusable, const string& pool_name):
  53.     m_Reporter(0, SQL_HANDLE_DBC, con)
  54. {
  55.     m_Link     = con;
  56.     m_Context  = cntx;
  57.     m_Reusable = reusable;
  58.     m_Pool     = pool_name;
  59.     m_Reporter.SetHandlerStack(&m_MsgHandlers);
  60. }
  61. bool CODBC_Connection::IsAlive()
  62. {
  63.     SQLINTEGER status;
  64.     SQLRETURN r= SQLGetConnectAttr(m_Link, SQL_ATTR_CONNECTION_DEAD, &status, SQL_IS_INTEGER, 0);
  65.     return ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (status == SQL_CD_FALSE));
  66. }
  67. CODBC_LangCmd* CODBC_Connection::xLangCmd(const string& lang_query,
  68.                                           unsigned int  nof_params)
  69. {
  70.     SQLHSTMT cmd;
  71.     SQLRETURN r= SQLAllocHandle(SQL_HANDLE_STMT, m_Link, &cmd);
  72.     if(r == SQL_ERROR) {
  73.         m_Reporter.ReportErrors();
  74.     }
  75.     CODBC_LangCmd* lcmd = new CODBC_LangCmd(this, cmd, lang_query, nof_params);
  76.     m_CMDs.Add(lcmd);
  77.     return lcmd;
  78. }
  79. CDB_LangCmd* CODBC_Connection::LangCmd(const string& lang_query,
  80.                                      unsigned int  nof_params)
  81. {
  82.     return Create_LangCmd(*(xLangCmd(lang_query, nof_params)));
  83. }
  84. CDB_RPCCmd* CODBC_Connection::RPC(const string& rpc_name,
  85.                                 unsigned int  nof_args)
  86. {
  87. #if 0
  88.     return 0;
  89. #else
  90.     SQLHSTMT cmd;
  91.     SQLRETURN r= SQLAllocHandle(SQL_HANDLE_STMT, m_Link, &cmd);
  92.     if(r == SQL_ERROR) {
  93.         m_Reporter.ReportErrors();
  94.     }
  95.     CODBC_RPCCmd* rcmd = new CODBC_RPCCmd(this, cmd, rpc_name, nof_args);
  96.     m_CMDs.Add(rcmd);
  97.     return Create_RPCCmd(*rcmd);
  98. #endif
  99. }
  100. CDB_BCPInCmd* CODBC_Connection::BCPIn(const string& table_name,
  101.                                     unsigned int  nof_columns)
  102. {
  103. #ifdef NCBI_OS_UNIX
  104.     return 0; // not implemented
  105. #else
  106.     if (!m_BCPable) {
  107.         throw CDB_ClientEx(eDB_Error, 410003, "CODBC_Connection::BCPIn",
  108.                            "No bcp on this connection");
  109.     }
  110.     CODBC_BCPInCmd* bcmd = new CODBC_BCPInCmd(this, m_Link, table_name, nof_columns);
  111.     m_CMDs.Add(bcmd);
  112.     return Create_BCPInCmd(*bcmd);
  113. #endif
  114. }
  115. CDB_CursorCmd* CODBC_Connection::Cursor(const string& cursor_name,
  116.                                       const string& query,
  117.                                       unsigned int  nof_params,
  118.                                       unsigned int  batch_size)
  119. {
  120. #if 0
  121.     return 0;
  122. #else
  123.     SQLHSTMT cmd;
  124.     SQLRETURN r= SQLAllocHandle(SQL_HANDLE_STMT, m_Link, &cmd);
  125.     if(r == SQL_ERROR) {
  126.         m_Reporter.ReportErrors();
  127.     }
  128.     CODBC_CursorCmd* ccmd = new CODBC_CursorCmd(this, cmd, cursor_name, query,
  129.                                                 nof_params);
  130.     m_CMDs.Add(ccmd);
  131.     return Create_CursorCmd(*ccmd);
  132. #endif
  133. }
  134. CDB_SendDataCmd* CODBC_Connection::SendDataCmd(I_ITDescriptor& descr_in,
  135.                                                size_t data_size, bool log_it)
  136. {
  137.     SQLHSTMT cmd;
  138.     SQLRETURN r= SQLAllocHandle(SQL_HANDLE_STMT, m_Link, &cmd);
  139.     if(r == SQL_ERROR) {
  140.         m_Reporter.ReportErrors();
  141.     }
  142.     CODBC_SendDataCmd* sd_cmd = 
  143.         new CODBC_SendDataCmd(this, cmd, 
  144.                               (CDB_ITDescriptor&)descr_in,  
  145.                               data_size, log_it);
  146.     m_CMDs.Add(sd_cmd);
  147.     return Create_SendDataCmd(*sd_cmd);
  148. }
  149. bool CODBC_Connection::SendData(I_ITDescriptor& desc, CDB_Image& img, bool log_it)
  150. {
  151.     SQLHSTMT cmd;
  152.     SQLRETURN r= SQLAllocHandle(SQL_HANDLE_STMT, m_Link, &cmd);
  153.     if(r == SQL_ERROR) {
  154.         m_Reporter.ReportErrors();
  155.     }
  156.     CODBC_Reporter lrep(&m_MsgHandlers, SQL_HANDLE_STMT, cmd);
  157.     SQLPOINTER p= (SQLPOINTER)2;
  158.     SQLINTEGER s= img.Size();
  159.     if((!ODBC_xSendDataPrepare(cmd, (CDB_ITDescriptor&)desc, s, false, log_it, p, lrep)) ||
  160.        (!ODBC_xSendDataGetId(cmd, &p, lrep))) {
  161.         throw CDB_ClientEx(eDB_Error, 410035, "CODBC_Connection::SendData",
  162.                            "can not prepare a command");
  163.     }
  164.     return x_SendData(cmd, img, lrep);
  165.     
  166. }
  167. bool CODBC_Connection::SendData(I_ITDescriptor& desc, CDB_Text& txt, bool log_it)
  168. {
  169.     SQLHSTMT cmd;
  170.     SQLRETURN r= SQLAllocHandle(SQL_HANDLE_STMT, m_Link, &cmd);
  171.     if(r == SQL_ERROR) {
  172.         m_Reporter.ReportErrors();
  173.     }
  174.     CODBC_Reporter lrep(&m_MsgHandlers, SQL_HANDLE_STMT, cmd);
  175.     SQLPOINTER p= (SQLPOINTER)2;
  176.     SQLINTEGER s= txt.Size();
  177.     if((!ODBC_xSendDataPrepare(cmd, (CDB_ITDescriptor&)desc, s, true, log_it, p, lrep)) ||
  178.        (!ODBC_xSendDataGetId(cmd, &p, lrep))) {
  179.         throw CDB_ClientEx(eDB_Error, 410035, "CODBC_Connection::SendData",
  180.                            "can not prepare a command");
  181.     }
  182.     return x_SendData(cmd, txt, lrep);
  183. }
  184. bool CODBC_Connection::Refresh()
  185. {
  186.     // close all commands first
  187.     while(m_CMDs.NofItems() > 0) {
  188.         CDB_BaseEnt* pCmd = static_cast<CDB_BaseEnt*> (m_CMDs.Get(0));
  189.         delete pCmd;
  190.         m_CMDs.Remove((int) 0);
  191.     }
  192.     return IsAlive();
  193. }
  194. const string& CODBC_Connection::ServerName() const
  195. {
  196.     return m_Server;
  197. }
  198. const string& CODBC_Connection::UserName() const
  199. {
  200.     return m_User;
  201. }
  202. const string& CODBC_Connection::Password() const
  203. {
  204.     return m_Passwd;
  205. }
  206. I_DriverContext::TConnectionMode CODBC_Connection::ConnectMode() const
  207. {
  208.     I_DriverContext::TConnectionMode mode = 0;
  209.     if ( m_BCPable ) {
  210.         mode |= I_DriverContext::fBcpIn;
  211.     }
  212.     if ( m_SecureLogin ) {
  213.         mode |= I_DriverContext::fPasswordEncrypted;
  214.     }
  215.     return mode;
  216. }
  217. bool CODBC_Connection::IsReusable() const
  218. {
  219.     return m_Reusable;
  220. }
  221. const string& CODBC_Connection::PoolName() const
  222. {
  223.     return m_Pool;
  224. }
  225. I_DriverContext* CODBC_Connection::Context() const
  226. {
  227.     return const_cast<CODBCContext*> (m_Context);
  228. }
  229. void CODBC_Connection::PushMsgHandler(CDB_UserHandler* h)
  230. {
  231.     m_MsgHandlers.Push(h);
  232. }
  233. void CODBC_Connection::PopMsgHandler(CDB_UserHandler* h)
  234. {
  235.     m_MsgHandlers.Pop(h);
  236. }
  237. CDB_ResultProcessor* CODBC_Connection::SetResultProcessor(CDB_ResultProcessor* rp)
  238. {
  239.     CDB_ResultProcessor* r= m_ResProc;
  240.     m_ResProc= rp;
  241.     return r;
  242. }
  243. void CODBC_Connection::Release()
  244. {
  245.     m_BR = 0;
  246.     // close all commands first
  247.     while(m_CMDs.NofItems() > 0) {
  248.         CDB_BaseEnt* pCmd = static_cast<CDB_BaseEnt*> (m_CMDs.Get(0));
  249.         delete pCmd;
  250.         m_CMDs.Remove((int) 0);
  251.     }
  252. }
  253. CODBC_Connection::~CODBC_Connection()
  254. {
  255.     if (Refresh()) {
  256.         switch(SQLDisconnect(m_Link)) {
  257.         case SQL_SUCCESS_WITH_INFO:
  258.         case SQL_ERROR:
  259.             m_Reporter.ReportErrors();
  260.         case SQL_SUCCESS:
  261.             break;
  262.         default:
  263.             throw CDB_ClientEx(eDB_Error, 410009, "CODBC_Connection::~CODBC_Connection",
  264.                                "SQLDisconnect failed (memory corruption suspected)");
  265.             
  266.         }
  267.     }
  268.     if(SQLFreeHandle(SQL_HANDLE_DBC, m_Link) == SQL_ERROR) {
  269.         m_Reporter.ReportErrors();
  270.     }
  271.         
  272. }
  273. void CODBC_Connection::DropCmd(CDB_BaseEnt& cmd)
  274. {
  275.     m_CMDs.Remove(static_cast<TPotItem> (&cmd));
  276. }
  277. static bool ODBC_xSendDataPrepare(SQLHSTMT cmd, CDB_ITDescriptor& descr_in,
  278.                                   SQLINTEGER size, bool is_text, bool logit, 
  279.           SQLPOINTER id, CODBC_Reporter& rep)
  280. {
  281.     string q= "update ";
  282.     q+= descr_in.TableName();
  283.     q+= " set ";
  284.     q+= descr_in.ColumnName();
  285.     q+= "=? where ";
  286.     q+= descr_in.SearchConditions();
  287.     //q+= " ;nset rowcount 0";
  288. #ifdef SQL_TEXTPTR_LOGGING
  289. if(!logit) {
  290. switch(SQLSetStmtAttr(cmd, SQL_TEXTPTR_LOGGING, /*SQL_SOPT_SS_TEXTPTR_LOGGING,*/
  291. (SQLPOINTER)SQL_TL_OFF, SQL_IS_INTEGER)) {
  292. case SQL_SUCCESS_WITH_INFO:
  293. case SQL_ERROR:
  294. rep.ReportErrors();
  295. default: break;
  296. }
  297. }
  298. #endif
  299. switch(SQLPrepare(cmd, (SQLCHAR*)q.c_str(), SQL_NTS)) {
  300. case SQL_SUCCESS_WITH_INFO:
  301. rep.ReportErrors();
  302. case SQL_SUCCESS: break;
  303. case SQL_ERROR:
  304. rep.ReportErrors();
  305. default: return false;
  306. }
  307. SQLSMALLINT par_type, par_dig, par_null;
  308.     SQLUINTEGER par_size;
  309. #if 0
  310. switch(SQLNumParams(cmd, &par_dig)) {
  311. case SQL_SUCCESS: break;
  312. case SQL_SUCCESS_WITH_INFO:
  313. case SQL_ERROR:
  314. rep.ReportErrors();
  315. default: return false;
  316. }
  317. #endif
  318. switch(SQLDescribeParam(cmd, 1, &par_type, &par_size, &par_dig, &par_null)){
  319. case SQL_SUCCESS_WITH_INFO:
  320. rep.ReportErrors();
  321. case SQL_SUCCESS: break;
  322. case SQL_ERROR:
  323. rep.ReportErrors();
  324. default: return false;
  325. }
  326.     SQLINTEGER p= SQL_LEN_DATA_AT_EXEC(size);
  327.     switch(SQLBindParameter(cmd, 1, SQL_PARAM_INPUT, 
  328.                      is_text? SQL_C_CHAR : SQL_C_BINARY, par_type,
  329.                      // is_text? SQL_LONGVARCHAR : SQL_LONGVARBINARY,
  330.                      size, 0, id, 0, &p)) {
  331. case SQL_SUCCESS_WITH_INFO:
  332. rep.ReportErrors();
  333. case SQL_SUCCESS: break;
  334. case SQL_ERROR:
  335. rep.ReportErrors();
  336. default: return false;
  337. }
  338.     
  339.     switch(SQLExecute(cmd)) {
  340.     case SQL_NEED_DATA:
  341.         return true;
  342.     case SQL_SUCCESS_WITH_INFO:
  343.     case SQL_ERROR:
  344.         rep.ReportErrors();
  345.     default:
  346.         return false;
  347.     }
  348. }
  349. static bool ODBC_xSendDataGetId(SQLHSTMT cmd, SQLPOINTER* id, 
  350.                                 CODBC_Reporter& rep)
  351. {
  352.     switch(SQLParamData(cmd, id)) {
  353.     case SQL_NEED_DATA:
  354.         return true;
  355.     case SQL_SUCCESS_WITH_INFO:
  356.     case SQL_ERROR:
  357.         rep.ReportErrors();
  358.     default:
  359.         return false;
  360.     }
  361. }
  362. bool CODBC_Connection::x_SendData(SQLHSTMT cmd, CDB_Stream& stream, CODBC_Reporter& rep)
  363. {
  364.     char buff[1801];
  365.     size_t s;
  366.     while((s= stream.Read(buff, sizeof(buff))) != 0) {
  367.         switch(SQLPutData(cmd, (SQLPOINTER)buff, (SQLINTEGER)s)) {
  368.         case SQL_SUCCESS_WITH_INFO:
  369.             rep.ReportErrors();
  370.         case SQL_NEED_DATA:
  371. continue;
  372.         case SQL_NO_DATA:
  373. return true;
  374.         case SQL_SUCCESS:
  375.             break;
  376.         case SQL_ERROR:
  377.             rep.ReportErrors();
  378.         default:
  379.             return false;
  380.         }
  381.     }
  382.     switch(SQLParamData(cmd, (SQLPOINTER*)&s)) {
  383. case SQL_SUCCESS_WITH_INFO: rep.ReportErrors();
  384.     case SQL_SUCCESS:           break;
  385. case SQL_NO_DATA:           return true;
  386.     case SQL_NEED_DATA: 
  387.     throw CDB_ClientEx(eDB_Error, 410044, "CODBC_Connection::SendData",
  388.                                "Not all the data were sent");
  389. case SQL_ERROR:             rep.ReportErrors();
  390. default:
  391. throw CDB_ClientEx(eDB_Error, 410045, "CODBC_Connection::SendData",
  392.                            "SQLParamData failed");
  393. }
  394.     for(;;) {
  395.         switch(SQLMoreResults(cmd)) {
  396.         case SQL_SUCCESS_WITH_INFO: rep.ReportErrors();
  397.         case SQL_SUCCESS:           continue;
  398.         case SQL_NO_DATA:           break;
  399.         case SQL_ERROR:             
  400.             rep.ReportErrors();
  401.             throw CDB_ClientEx(eDB_Error, 410014, "CODBC_Connection::SendData",
  402.                                "SQLMoreResults failed");
  403.         default:
  404.             throw CDB_ClientEx(eDB_Error, 410015, "CODBC_Connection::SendData",
  405.                                "SQLMoreResults failed (memory corruption suspected)");
  406.         }
  407. break;
  408.     }
  409.     return true;
  410. }
  411.         
  412. void CODBC_Connection::ODBC_SetTimeout(SQLUINTEGER nof_secs) {}
  413. void CODBC_Connection::ODBC_SetTextImageSize(SQLUINTEGER nof_bytes) {}
  414. /////////////////////////////////////////////////////////////////////////////
  415. //
  416. //  CODBC_SendDataCmd::
  417. //
  418. CODBC_SendDataCmd::CODBC_SendDataCmd(CODBC_Connection* conn, 
  419.                                      SQLHSTMT cmd, 
  420.                                      CDB_ITDescriptor& descr,
  421.                                      size_t nof_bytes, bool logit) :
  422.     m_Connect(conn), m_Cmd(cmd), m_Bytes2go(nof_bytes),
  423.     m_Reporter(&conn->m_MsgHandlers, SQL_HANDLE_STMT, cmd)
  424. {
  425.     SQLPOINTER p= (SQLPOINTER)1;
  426.     if((!ODBC_xSendDataPrepare(cmd, descr, (SQLINTEGER)nof_bytes,
  427.                               false, logit, p, m_Reporter)) ||
  428.        (!ODBC_xSendDataGetId(cmd, &p, m_Reporter))) {
  429.         throw CDB_ClientEx(eDB_Error, 410035, "CODBC_SendDataCmd::CODBC_SendDataCm",
  430.                            "can not prepare a command");
  431.     }   
  432. }
  433. size_t CODBC_SendDataCmd::SendChunk(const void* chunk_ptr, size_t nof_bytes)
  434. {
  435.     if(nof_bytes > m_Bytes2go) nof_bytes= m_Bytes2go;
  436.     if(nof_bytes < 1) return 0;
  437.     switch(SQLPutData(m_Cmd, (SQLPOINTER)chunk_ptr, (SQLINTEGER)nof_bytes)) {
  438.     case SQL_SUCCESS_WITH_INFO:
  439.         m_Reporter.ReportErrors();
  440.     case SQL_NEED_DATA:
  441.     case SQL_NO_DATA:
  442.     case SQL_SUCCESS:
  443.         m_Bytes2go-= nof_bytes;
  444.         if(m_Bytes2go == 0) break;
  445.         return nof_bytes;
  446.     case SQL_ERROR:
  447.         m_Reporter.ReportErrors();
  448.     default:
  449.         return 0;
  450.     }
  451.     size_t s;
  452.     switch(SQLParamData(m_Cmd, (SQLPOINTER*)&s)) {
  453. case SQL_SUCCESS_WITH_INFO: m_Reporter.ReportErrors();
  454.     case SQL_SUCCESS:           break;
  455. case SQL_NO_DATA:           break;
  456.     case SQL_NEED_DATA: 
  457.     throw CDB_ClientEx(eDB_Error, 410044, "CODBC_Connection::SendChunk",
  458.                                "Not all the data were sent");
  459. case SQL_ERROR:             m_Reporter.ReportErrors();
  460. default:
  461. throw CDB_ClientEx(eDB_Error, 410045, "CODBC_Connection::SendChunk",
  462.                            "SQLParamData failed");
  463. }
  464.     for(;;) {
  465.         switch(SQLMoreResults(m_Cmd)) {
  466.         case SQL_SUCCESS_WITH_INFO: m_Reporter.ReportErrors();
  467.         case SQL_SUCCESS:           continue;
  468.         case SQL_NO_DATA:           break;
  469.         case SQL_ERROR:             
  470.             m_Reporter.ReportErrors();
  471.             throw CDB_ClientEx(eDB_Error, 410014, "CODBC_SendDataCmd::SendChunk",
  472.                                "SQLMoreResults failed");
  473.         default:
  474.             throw CDB_ClientEx(eDB_Error, 410015, "CODBC_SendDataCmd::SendChunk",
  475.                                "SQLMoreResults failed (memory corruption suspected)");
  476.         }
  477. break;
  478.     }
  479.     return nof_bytes;
  480. }
  481. void CODBC_SendDataCmd::Release()
  482. {
  483.     m_BR = 0;
  484.     if (m_Bytes2go > 0) {
  485.         xCancel();
  486.         m_Bytes2go = 0;
  487.     }
  488.     m_Connect->DropCmd(*this);
  489.     delete this;
  490. }
  491. CODBC_SendDataCmd::~CODBC_SendDataCmd()
  492. {
  493.     if (m_Bytes2go > 0)
  494.         xCancel();
  495.     if (m_BR)
  496.         *m_BR = 0;
  497.     SQLFreeHandle(SQL_HANDLE_STMT, m_Cmd);
  498. }
  499. void CODBC_SendDataCmd::xCancel()
  500. {
  501.     switch(SQLFreeStmt(m_Cmd, SQL_CLOSE)) {
  502.     case SQL_SUCCESS_WITH_INFO: m_Reporter.ReportErrors();
  503.     case SQL_SUCCESS:           break;
  504.     case SQL_ERROR:             m_Reporter.ReportErrors();
  505.     default:                    return;
  506.     }
  507.     SQLFreeStmt(m_Cmd, SQL_RESET_PARAMS);
  508. }
  509. END_NCBI_SCOPE
  510. /*
  511.  * ===========================================================================
  512.  * $Log: connection.cpp,v $
  513.  * Revision 1000.1  2004/06/01 19:21:40  gouriano
  514.  * PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.5
  515.  *
  516.  * Revision 1.5  2004/05/17 21:16:05  gorelenk
  517.  * Added include of PCH ncbi_pch.hpp
  518.  *
  519.  * Revision 1.4  2003/06/05 16:02:04  soussov
  520.  * adds code for DumpResults and for the dumped results processing
  521.  *
  522.  * Revision 1.3  2003/05/05 20:47:45  ucko
  523.  * Check HAVE_ODBCSS_H; regardless, disable BCP on Unix, since even
  524.  * DataDirect's implementation lacks the relevant bcp_* functions.
  525.  *
  526.  * Revision 1.2  2002/07/05 20:47:56  soussov
  527.  * fixes bug in SendData
  528.  *
  529.  *
  530.  * ===========================================================================
  531.  */