Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ODBCStatement.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file ODBCStatement.cxx
1 // $Id: ODBCStatement.cxx,v 1.1.1.1 2004/02/18 20:58:02 dave Exp $
2 //*-- Author : Valeriy Onuchin 14/02/2000
3 //
4 
5 /**************************************************************************
6 
7  ROOT wrappers of libodbc++ library
8 
9  Copyright (C) 1999-2000 Manush Dodunekov <manush@stendahls.net>
10 
11  This library is free software; you can redistribute it and/or
12  modify it under the terms of the GNU Library General Public
13  License as published by the Free Software Foundation; either
14  version 2 of the License, or (at your option) any later version.
15 
16  This library is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  Library General Public License for more details.
20 
21  You should have received a copy of the GNU Library General Public License
22  along with this library; see the file COPYING. If not, write to
23  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24  Boston, MA 02111-1307, USA.
25 
26 **************************************************************************/
27 
29 //
30 // The object used for executing a static SQL statement and
31 // obtaining the results produced by it.
32 //
33 // Only one TSQLResultSet per ODBCStatement can be open at any point
34 // in time. Therefore, if the reading of one TSQLResultSet is
35 // interleaved with the reading of another, each must have been
36 // generated by different ODBCStatements. All statement execute
37 // methods implicitly close a statment's current TSQLResultSet if
38 // an open one exists.
39 //
40 // See also:
41 // TSQLConnection::CreateStatement(), TSQLResultSet
42 // TSQLCallableStatement TSQLPreparedStatement
43 //Begin_Html
44 /*
45 <P>
46  The <TT>ODBCStatement</TT> class encapsulates SQL queries to your database.
47 Using several methods, these calls return objects that contain the
48 results of your SQL query. When you execute an SQL query, the data
49 that is returned to you is commonly called the result set. You can
50 choose from several result sets, depending on your needs:
51 <UL>
52 <LI><TT>TSQLResultSet* ExecuteQuery( const TString& sqlStatement)<BR></TT>
53 This method sends the SQL query contained in <TT>sqlStatement</TT>
54 and returns a single set of results. This method is best used
55 in sending <TT>SELECT</TT> statements. These statements typically
56 return a result set. This method implicitly deletes previous resultset.
57 
58 <LI><TT>Int_t ExecuteUpdate( const TString& sqlStatement )<BR></TT>
59 This method sends the SQL query contained in <TT>sqlStatement</TT>
60 and returns an integer. This method is useful when you send SQL
61 <TT>INSERT</TT>s, <TT>DELETE</TT>s, and <TT>UPDATE</TT>s. These commands return
62 a count of rows that were affected by your query. This statement
63 should not be used for queries that return result sets.
64 
65 <LI><TT>Bool_t Execute( const TString& sqlStatement )<BR></TT>
66 This method sends the <TT>sqlStatement</TT> to the database and returns
67 <TT>kTRUE</TT> if the statement returns a result set or <TT>kFALSE</TT> if the
68 statement returns an integer. This method is best used when multiple
69 result sets can be returned.
70 </UL>
71 <P>
72 Use the following methods to easily navigate the results a query returns:
73 <UL>
74 <LI><TT>Bool_t GetMoreResults()<BR> </TT>
75 This moves to the next result set in the <TT>ODBCStatement</TT>. This,
76 like the <TT>Execute()</TT> method, returns <TT>kTRUE</TT> if the next
77 result is a result set or <TT>kFALSE</TT> if it is an integer.
78 If you have already retrieved a <TT>TSQLResultSet</TT> from the
79 <TT>ODBCStatement</TT>, this method will close it before returning.
80 
81 <LI><TT>TSQLResultSet* GetResultSet()<BR></TT>
82 This method returns to you a result set in a <TT>TSQLResultSet</TT>
83 object. This result set is the current result set.
84 
85 <LI><TT>Int_t GetUpdateCount()<BR></TT>
86 This method returns to you the integer result that an
87 <TT>Execute()</TT> method returned.
88 </UL>
89 <P>
90 */
91 //End_Html
93 
94 #include "ODBCStatement.h"
95 #include "ODBCResultSet.h"
96 #include <RDBC/TSQLConnection.h>
97 #include <TList.h>
98 #include <RDBC/odbc++/statement.h>
99 #include <RDBC/odbc++/resultset.h>
100 
101 using namespace odbc;
102 
104 
105 
106 //___________________________________________________________________
108  TSQLStatement(con,imp)
109 {
110  // ctor
111 }
112 
113 //___________________________________________________________________
115 {
116  // Destructor.
117  //
118  // Note: When a ODBCStatement is closed, its current
119  // TSQLResultSet, if one exists, is also closed.
120  //
121 
123 
124  try {
125  if(imp) delete imp;
126  } catch(odbc::SQLException& e) {
129  e.getErrorCode()) );
130  }
131 
132  // implementation part of fCurrentResult is deleted with statement
133  if(fCurrentResult) ((ODBCResultSet*)fCurrentResult)->fImp = 0;
134  fImp = 0;
135 }
136 
137 //___________________________________________________________________
139 {
140  // Executes a SQL statement that returns a single TSQLResultSet
141  //
142  // This method also implicitly closes current TSQLResultSet
143  //
144  // Returns:
145  // a TSLResultSet that contains the data produced by the query;
146  // NULL - in case of error
147  //
148  // Throws:
149  // TSQLException - if a database access error occurs
150 
151  if(!fImp) { Destroyed(); return 0; }
153  odbc::ResultSet* rs = 0;
154  ClearWarnings();
155 
156  if(fCurrentResult) { delete fCurrentResult; fCurrentResult = 0; }
157 
158  try {
159  rs = stmt->executeQuery(ODBCXX_STRING_C(sql.Data()));
160  } catch(odbc::SQLException& e) {
163  e.getErrorCode()) );
164  if(rs) delete rs;
165  fCurrentResult = 0;
166  return 0;
167  }
168 
169  return fCurrentResult = new ODBCResultSet(this,(void*)rs);;
170 }
171 
172 //___________________________________________________________________
173 Bool_t ODBCStatement::Execute( const TString& sql )
174 {
175  // Executes a SQL statement that may return multiple results.
176  // Under some (uncommon) situations a single SQL statement may
177  // return multiple result sets and/or update counts. Normally you
178  // can ignore this unless you are (1) executing a stored
179  // procedure that you know may return multiple results or (2) you
180  // are dynamically executing an unknown SQL string. The methods
181  // execute, GetMoreResults(), GetResultSet(), and GetUpdateCount()
182  // let you navigate through multiple results.
183  // The execute method executes a SQL statement and indicates the
184  // form of the first result. You can then use GetResultSet() or
185  // GetUpdateCount() to retrieve the result, and GetMoreResults()
186  // to move to any subsequent result(s).
187  //
188  // Parameters:
189  // sql - any SQL statement
190  // Returns:
191  // kTRUE if the next result is a TSQLResultSet;
192  // kFALSE if it is an update count or there are no more
193  // results
194  // Throws:
195  // TSQLException - if a database access error occurs
196  // See Also:
197  // GetResultSet(), GetUpdateCount(), GetMoreResults()
198 
199  if(!fImp) { Destroyed(); return kFALSE; }
200 
201  Bool_t return_value = kFALSE;
202  ClearWarnings();
204 
205  if(fCurrentResult) { delete fCurrentResult; fCurrentResult = 0; }
206 
207  try {
208  return_value = (Bool_t)stmt->execute(ODBCXX_STRING_C(sql.Data()));
209  } catch(odbc::SQLException& e) {
212  e.getErrorCode()) );
213  return_value = kFALSE;
214  }
215  return return_value;
216 }
217 
218 //___________________________________________________________________
219 Int_t ODBCStatement::ExecuteUpdate( const TString& sql )
220 {
221  // Executes an SQL INSERT, UPDATE or DELETE statement.
222  // In addition, SQL statements that return nothing,
223  // such as SQL DDL statements, can be executed.
224  //
225  // Parameters:
226  // sql - a SQL INSERT, UPDATE or DELETE statement or
227  // a SQL statement that returns nothing
228  //
229  // Returns:
230  // either the row count for INSERT, UPDATE or DELETE or
231  // 0 for SQL statements that return nothing
232  // Throws:
233  // TSQLException - if a database access error occurs
234 
235  if(!fImp) { Destroyed(); return 0; }
236 
237  Int_t return_value = 0;
238  ClearWarnings();
240 
241  if(fCurrentResult) { delete fCurrentResult; fCurrentResult = 0; }
242 
243  try {
244  return_value = stmt->executeUpdate(ODBCXX_STRING_C(sql.Data()));
245  } catch(odbc::SQLException& e) {
248  e.getErrorCode()) );
249  return_value = 0;
250  }
251  return return_value;
252 }
253 
254 //___________________________________________________________________
256 {
257  // Returns the current result as a TSQLResultSet object.
258  // This method should be called only once per result.
259  //
260  // This method also implicitly closes any current TSQLResultSet
261  //
262  // Returns:
263  // the current result as a TSQLResultSet; null if the result
264  // is an update count or there are no more results
265  // Throws:
266  // TSQLException - if a database access error occurs
267  // See Also:
268  // Execute(const TString&)
269 
270  if(!fImp) { Destroyed(); return 0; }
271  odbc::ResultSet* rs = 0;
273 
274  if(fCurrentResult) { delete fCurrentResult; fCurrentResult = 0; }
275 
276  try {
277  rs = stmt->getResultSet();
278  } catch(odbc::SQLException& e) {
281  e.getErrorCode()) );
282  if(rs) delete rs;
283  fCurrentResult = 0;
284  return 0;
285  }
286 
287  return fCurrentResult = new ODBCResultSet(this,(void*)rs);
288 }
289 
290 //___________________________________________________________________
292 {
293  // Returns the current result as an update count;
294  // if there are no more results, -1 is returned.
295  // This method should be called only once per result.
296  //
297  // Returns:
298  // the current result as an update count; -1 if it is a
299  // TSQLResultSet or there are no more results
300  // Throws:
301  // TSQLException - if a database access error occurs
302  // See Also:
303  // Execute(const TString&)
304 
305  if(!fImp) { Destroyed(); return 0; }
306 
307  Int_t return_value = 0;
309 
310  try {
311  return_value = stmt->getUpdateCount();
312  } catch(odbc::SQLException& e) {
315  e.getErrorCode()) );
316  return 0;
317  }
318  return return_value;
319 }
320 
321 //___________________________________________________________________
323 {
324  // Moves to a ODBCStatement's next result. It returns kTRUE if
325  // this result is a TSQLResultSet.
326  //
327  // There are no more results when
328  // (!GetMoreResults() && (GetUpdateCount() == -1)
329  //
330  // Returns:
331  // kTRUE if the next result is a TSQLResultSet;
332  // kFALSE if it is an update count or there are no more results
333  //
334  // Throws:
335  // TSQLException - if a database access error occurs
336  // See Also:
337  // Execute(const TString&)
338 
339  Bool_t return_value = kFALSE;
340 
341  if(!fImp) { Destroyed(); return return_value; }
343 
344  try {
345  return_value = (Bool_t)stmt->getMoreResults();
346  } catch(odbc::SQLException& e) {
349  e.getErrorCode()) );
350  return kFALSE;
351  }
352  return return_value;
353 }
354 
355 //___________________________________________________________________
357 {
358  // Returns the maximum number of bytes allowed for any column
359  // value. This limit is the maximum number of bytes that can be
360  // returned for any column value. The limit applies only to
361  // kBINARY, kVARBINARY, kLONGVARBINARY, kCHAR, kVARCHAR, and
362  // kLONGVARCHAR columns (see TSQLTypes.h). If the limit is exceeded,
363  // the excess data is silently discarded.
364  //
365  // Returns:
366  // the current max column size limit; zero means unlimited
367  // Throws:
368  // TSQLException - if a database access error occurs
369 
370  if(!fImp) { Destroyed(); return 0; }
371 
372  Int_t return_value = 0;
374 
375  try {
376  return_value = stmt->getMaxFieldSize();
377  } catch(odbc::SQLException& e) {
380  e.getErrorCode()) );
381  return 0;
382  }
383  return return_value;
384 }
385 
386 //___________________________________________________________________
388 {
389  // Sets the limit for the maximum number of bytes in a column to
390  // the given number of bytes. This is the maximum number of bytes
391  // that can be returned for any column value. This limit applies
392  // only to kBINARY, kVARBINARY, kLONGVARBINARY, kCHAR, kVARCHAR,
393  // and kLONGVARCHAR fields (see TSQLTypes.h) . If the limit is exceeded,
394  // the excess data is silently discarded. For maximum portability,
395  // use values greater than 256.
396  //
397  // Parameters:
398  // max - the new max column size limit; zero means unlimited
399  // Throws:
400  // TSQLException - if a database access error occurs
401 
402  if(!fImp) { Destroyed(); return; }
404 
405  try {
406  stmt->setMaxFieldSize(max);
407  } catch(odbc::SQLException& e) {
410  e.getErrorCode()) );
411  }
412 }
413 
414 //___________________________________________________________________
416 {
417  // Retrieves the maximum number of rows that a TSQLResultSet can
418  // contain. If the limit is exceeded, the excess rows are silently
419  // dropped.
420  //
421  // Returns:
422  // the current max row limit; zero means unlimited
423  // Throws:
424  // TSQLException - if a database access error occurs
425 
426  if(!fImp) { Destroyed(); return 0; }
427 
428  Int_t return_value = 0;
430 
431  try {
432  return_value = stmt->getMaxRows();
433  } catch(odbc::SQLException& e) {
436  e.getErrorCode()) );
437  return 0;
438  }
439  return return_value;
440 }
441 
442 //___________________________________________________________________
443 void ODBCStatement::SetMaxRows( Int_t max )
444 {
445  // Sets the limit for the maximum number of rows that any
446  // TSQLResultSet can contain to the given number. If the limit is
447  // exceeded, the excess rows are silently dropped.
448  //
449  // Parameters:
450  // max - the new max rows limit; zero means unlimited
451  // Throws:
452  // TSQLException - if a database access error occurs
453 
454  if(!fImp) { Destroyed(); return; }
456 
457  try {
458  stmt->setMaxRows(max);
459  } catch(odbc::SQLException& e) {
462  e.getErrorCode()) );
463  }
464 }
465 
466 //___________________________________________________________________
468 {
469  // Sets escape processing on or off. If escape scanning is on
470  // (the default), the driver will do escape substitution before
471  // sending the SQL to the database.
472  //
473  // Note:
474  // Since prepared statements have usually been parsed prior to
475  // making this call, disabling escape processing for prepared
476  // statements will have no effect.
477  //
478  // Parameters:
479  // enable - kTRUE to enable; kFALSE to disable
480  // Throws:
481  // TSQLException - if a database access error occurs
482 
483  if(!fImp) { Destroyed(); return; }
485 
486  try {
487  stmt->setEscapeProcessing(enable);
488  } catch(odbc::SQLException& e) {
491  e.getErrorCode()) );
492  }
493 }
494 
495 //___________________________________________________________________
497 {
498  // Returns if escape processing is on or off.
499  // If escape scanning is on (the default), the driver will do escape
500  // substitution before sending the SQL to the database.
501  //
502  // Note:
503  // Since prepared statements have usually been parsed prior to
504  // making this call, disabling escape processing for prepared
505  // statements will have no effect.
506  //
507  // Parameters:
508  // enable - kTRUE to enable; kFALSE to disable
509  // Throws:
510  // TSQLException - if a database access error occurs
511 
512  if(!fImp) { Destroyed(); return kFALSE; }
513 
514  Bool_t return_value = kFALSE;
516 
517  try {
518  return_value = stmt->getEscapeProcessing();
519  } catch(odbc::SQLException& e) {
522  e.getErrorCode()) );
523  return kFALSE;
524  }
525  return return_value;
526 }
527 
528 //___________________________________________________________________
530 {
531  // Retrieves the number of seconds the driver will wait for a
532  // ODBCStatement to execute. If the limit is exceeded, a
533  // TSQLException is thrown.
534  //
535  // Returns:
536  // the current query timeout limit in seconds; zero means
537  // unlimited
538  // Throws:
539  // TSQLException - if a database access error occurs
540 
541  Int_t return_value = 0;
542 
543  if(!fImp) { Destroyed(); return return_value; }
545 
546  try {
547  return_value = stmt->getQueryTimeout();
548  } catch(odbc::SQLException& e) {
551  e.getErrorCode()) );
552  return 0;
553  }
554  return return_value;
555 }
556 
557 //___________________________________________________________________
558 void ODBCStatement::SetQueryTimeout( Int_t seconds )
559 {
560  // Sets the number of seconds the driver will wait for a
561  // ODBCStatement to execute to the given number of seconds.
562  // If the limit is exceeded, a TSQLException is thrown.
563  //
564  // Parameters:
565  // seconds - the new query timeout limit in seconds;
566  // zero means unlimited
567  // Throws:
568  // TSQLException - if a database access error occurs
569 
570  if(!fImp) { Destroyed(); return; }
572 
573  try {
574  stmt->setQueryTimeout(seconds);
575  } catch(odbc::SQLException& e) {
578  e.getErrorCode()) );
579  }
580 }
581 
582 //___________________________________________________________________
584 {
585  // Cancels this statement object if both the DBMS and driver
586  // support aborting an SQL statement. This method can be used by
587  // one thread to cancel a statement that is being executed by
588  // another thread.
589  //
590  // Throws:
591  // TSQLException - if a database access error occurs
592 
593  if(!fImp) { Destroyed(); return; }
595 
596  try {
597  stmt->cancel();
598  } catch(odbc::SQLException& e) {
601  e.getErrorCode()) );
602  }
603 }
604 
605 //___________________________________________________________________
607 {
608  // Avoid using this method. Use delete ODBCStatement instead.
609  //
610  // Note: When a ODBCStatement is closed, its current
611  // TSQLResultSet, if one exists, is also closed.
612  //
613  // Throws:
614  // TSQLException - if a database access error occurs
615 
616  if(!fImp) { Destroyed(); return; }
617 
618  try {
619  if(fCurrentResult) {
620  delete fCurrentResult;
621  fCurrentResult = 0;
622  }
623  ClearBatch();
624  SafeDelete(fBatches);
625 
627  if(imp) delete imp;
628  } catch(odbc::SQLException& e) {
631  e.getErrorCode()) );
632  }
633  fImp = 0;
634  Destroyed();
635 }
636 
637 //___________________________________________________________________
638 void ODBCStatement::SetCursorName( const TString& name )
639 {
640  // Defines the SQL cursor name that will be used by subsequent
641  // ODBCStatement execute methods. This name can then be used in
642  // SQL positioned update/delete statements to identify the
643  // current row in the TSQLResultSet generated by this statement.
644  // If the database doesn't support positioned update/delete,
645  // this method is a noop. To insure that a cursor has the proper
646  // isolation level to support updates, the cursor's SELECT
647  // statement should be of the form 'SELECT FOR UPDATE ...'. If
648  // the 'FOR UPDATE' phrase is omitted, positioned updates may
649  // fail.
650  //
651  // Note: By definition, positioned update/delete execution must
652  // be done by a different ODBCStatement than the one which
653  // generated the TSQLResultSet being used for positioning.
654  // Also, cursor names must be unique within a connection.
655  //
656  // Parameters:
657  // name - the new cursor name, which must be unique within
658  // a connection
659  // Throws:
660  // TSQLException - if a database access error occurs
661 
662  if(!fImp) { Destroyed(); return; }
664 
665  try {
666  stmt->setCursorName(ODBCXX_STRING_C(name.Data()));
667  } catch(odbc::SQLException& e) {
670  e.getErrorCode()) );
671  }
672 }
673 
674 //___________________________________________________________________
675 void ODBCStatement::SetFetchDirection( Int_t /* direction */ )
676 {
677  // Gives the driver a hint as to the direction in which the
678  // rows in a result set will be processed. The hint applies only
679  // to result sets created using this statement object.
680  // The default value is TSQLResultSet::kTYPE_FORWARD_ONLY
681  //
682  // Note that this method sets the default fetch direction for
683  // result sets generated by this statement object.
684  //
685  // Parameters:
686  // direction - the initial direction for processing rows
687  // Throws:
688  // TSQLException - if a database access error occurs or the
689  // given direction is not one of
690  //
691 
692  if(!fImp) { Destroyed(); return; }
693 }
694 
695 //___________________________________________________________________
697 {
698  // Retrieves the direction for fetching rows from database
699  // tables that is the default for result sets generated from this
700  // statement object. If this statement object has not set
701  // a fetch direction by calling the method SetFetchDirection(),
702  // the return value is implementation-specific.
703  //
704  // Returns:
705  // the default fetch direction for result sets generated
706  // from this statement object
707  // Throws:
708  // TSQLException - if a database access error occurs
709 
710  return 0;
711 }
712 
713 //___________________________________________________________________
714 void ODBCStatement::SetFetchSize( Int_t /* rows */ )
715 {
716  // Gives the driver a hint as to the number of rows that
717  // should be fetched from the database when more rows are needed.
718  // The number of rows specified affects only result sets created
719  // using this statement. If the value specified is zero, then the
720  // hint is ignored. The default value is zero.
721  //
722  // Parameters:
723  // rows - the number of rows to fetch
724  // Throws:
725  // TSQLException - if a database access error occurs, or
726  // the condition 0 <= rows <= GetMaxRows() is not satisfied.
727 
728  if(!fImp) { Destroyed(); return; }
729 }
730 
731 //___________________________________________________________________
733 {
734  // Retrieves the number of result set rows that is the default
735  // fetch size for result sets generated from this ODBCStatement
736  // object. If this statement object has not set a fetch size
737  // by calling the method SetFetchSize(), the return value is
738  // implementation-specific.
739  //
740  // Returns:
741  // the default fetch size for result sets generated from
742  // this statement object
743  // Throws:
744  // TSQLException - if a database access error occurs
745 
746  Int_t return_value = 0;
747 
748  if(!fImp) { Destroyed(); return return_value; }
750 
751  try {
752  return_value = stmt->getFetchSize();
753  } catch(odbc::SQLException& e) {
756  e.getErrorCode()) );
757  return 0;
758  }
759  return return_value;
760 }
761 
762 //___________________________________________________________________
764 {
765  // Retrieves the result set concurrency.
766  //
767  // enum EResultSetConcurrency{
768  // kCONCUR_READ_ONLY,
769  // kCONCUR_UPDATABLE
770  // };
771 
772  Int_t return_value = 0;
773 
774  if(!fImp) { Destroyed(); return return_value; }
776 
777  try {
778  return_value = stmt->getResultSetConcurrency();
779  } catch(odbc::SQLException& e) {
782  e.getErrorCode()) );
783  return 0;
784  }
785  return return_value;
786 }
787 
788 //___________________________________________________________________
790 {
791  // Determine the result set type.
792  //
793  // enum EResultSetType{
794  // kTYPE_FORWARD_ONLY,
795  // kTYPE_SCROLL_INSENSITIVE,
796  // kTYPE_SCROLL_SENSITIVE
797  // };
798  //
799 
800  Int_t return_value = 0;
801 
802  if(!fImp) { Destroyed(); return return_value; }
804 
805  try {
806  return_value = stmt->getResultSetType();
807  } catch(odbc::SQLException& e) {
810  e.getErrorCode()) );
811  return 0;
812  }
813  return return_value;
814 }
815 
816 //___________________________________________________________________
817 void ODBCStatement::AddBatch( const TString& sql )
818 {
819  // Adds a SQL command to the current batch of commmands for
820  // the statement. This method is optional.
821  //
822  // Parameters:
823  // sql - typically this is a static SQL INSERT or UPDATE
824  // statement
825  // Throws:
826  // TSQLException - if a database access error occurs, or
827  // the driver does not support batch statements
828 
829 }
830 
831 //___________________________________________________________________
833 {
834  // Makes the set of commands in the current batch empty. This
835  // method is optional.
836  //
837  // Throws:
838  // TSQLException - if a database access error occurs or
839  // the driver does not support batch statements
840 
841 }
842 
843 //___________________________________________________________________
845 {
846  // Submits a batch of commands to the database for execution.
847  // This method is optional.
848  //
849  // Returns:
850  // an array of update counts containing one element for
851  // each command in the batch. The array is ordered
852  // according to the order in which commands were inserted
853  // into the batch.
854  //
855  // Throws:
856  // TSQLException - if a database access error occurs or
857  // the driver does not support batch statements
858 
859  return 0;
860 }