]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/adodb.inc.php
elseif
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / adodb.inc.php
1 <?php
2 /*
3  * Set tabs to 4 for best viewing.
4  *
5  * Latest version is available at http://php.weblogs.com/adodb
6  *
7  * This is the main include file for ADOdb.
8  * Database specific drivers are stored in the adodb/drivers/adodb-*.inc.php
9  *
10  * The ADOdb files are formatted so that doxygen can be used to generate documentation.
11  * Doxygen is a documentation generation tool and can be downloaded from http://doxygen.org/
12  */
13
14 /**
15     \mainpage
16
17      @version V4.22 15 Apr 2004 (c) 2000-2004 John Lim (jlim\@natsoft.com.my). All rights reserved.
18
19     Released under both BSD license and Lesser GPL library license. You can choose which license
20     you prefer.
21
22     PHP's database access functions are not standardised. This creates a need for a database
23     class library to hide the differences between the different database API's (encapsulate
24     the differences) so we can easily switch databases.
25
26     We currently support MySQL, Oracle, Microsoft SQL Server, Sybase, Sybase SQL Anywhere, DB2,
27     Informix, PostgreSQL, FrontBase, Interbase (Firebird and Borland variants), Foxpro, Access,
28     ADO, SAP DB, SQLite and ODBC. We have had successful reports of connecting to Progress and
29     other databases via ODBC.
30
31     Latest Download at http://php.weblogs.com/adodb<br>
32     Manual is at http://php.weblogs.com/adodb_manual
33
34  */
35
36  if (!defined('_ADODB_LAYER')) {
37      define('_ADODB_LAYER',1);
38
39     //==========================================================================
40     // CONSTANT DEFINITIONS
41     //==========================================================================
42
43     /**
44      * Set ADODB_DIR to the directory where this file resides...
45      * This constant was formerly called $ADODB_RootPath
46      */
47     if (!defined('ADODB_DIR')) define('ADODB_DIR',dirname(__FILE__));
48
49     //==========================================================================
50     // GLOBAL VARIABLES
51     //==========================================================================
52
53     GLOBAL
54         $ADODB_vers,         // database version
55         $ADODB_COUNTRECS,    // count number of records returned - slows down query
56         $ADODB_CACHE_DIR,    // directory to cache recordsets
57         $ADODB_EXTENSION,   // ADODB extension installed
58         $ADODB_COMPAT_PATCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF
59          $ADODB_FETCH_MODE;    // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default...
60
61     //==========================================================================
62     // GLOBAL SETUP
63     //==========================================================================
64
65     $ADODB_EXTENSION = defined('ADODB_EXTENSION');
66     if (!$ADODB_EXTENSION || ADODB_EXTENSION < 4.0) {
67
68         define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>');
69
70     // allow [ ] @ ` " and . in table names
71         define('ADODB_TABLE_REGEX','([]0-9a-z_\"\`\.\@\[-]*)');
72
73     // prefetching used by oracle
74         if (!defined('ADODB_PREFETCH_ROWS')) define('ADODB_PREFETCH_ROWS',10);
75
76
77     /*
78     Controls ADODB_FETCH_ASSOC field-name case. Default is 2, use native case-names.
79     This currently works only with mssql, odbc, oci8po and ibase derived drivers.
80
81          0 = assoc lowercase field names. $rs->fields['orderid']
82         1 = assoc uppercase field names. $rs->fields['ORDERID']
83         2 = use native-case field names. $rs->fields['OrderID']
84     */
85
86         define('ADODB_FETCH_DEFAULT',0);
87         define('ADODB_FETCH_NUM',1);
88         define('ADODB_FETCH_ASSOC',2);
89         define('ADODB_FETCH_BOTH',3);
90
91         if (!defined('TIMESTAMP_FIRST_YEAR')) define('TIMESTAMP_FIRST_YEAR',100);
92
93         if (strnatcmp(PHP_VERSION,'4.3.0')>=0) {
94             define('ADODB_PHPVER',0x4300);
95         } elseif (strnatcmp(PHP_VERSION,'4.2.0')>=0) {
96             define('ADODB_PHPVER',0x4200);
97         } elseif (strnatcmp(PHP_VERSION,'4.0.5')>=0) {
98             define('ADODB_PHPVER',0x4050);
99         } else {
100             define('ADODB_PHPVER',0x4000);
101         }
102     }
103
104     //if (!defined('ADODB_ASSOC_CASE')) define('ADODB_ASSOC_CASE',2);
105
106
107     /**
108          Accepts $src and $dest arrays, replacing string $data
109     */
110     function ADODB_str_replace($src, $dest, $data)
111     {
112         if (ADODB_PHPVER >= 0x4050) return str_replace($src,$dest,$data);
113
114         $s = reset($src);
115         $d = reset($dest);
116         while ($s !== false) {
117             $data = str_replace($s,$d,$data);
118             $s = next($src);
119             $d = next($dest);
120         }
121         return $data;
122     }
123
124     function ADODB_Setup()
125     {
126     GLOBAL
127         $ADODB_vers,         // database version
128         $ADODB_COUNTRECS,    // count number of records returned - slows down query
129         $ADODB_CACHE_DIR,    // directory to cache recordsets
130          $ADODB_FETCH_MODE;
131
132         $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT;
133
134         if (!isset($ADODB_CACHE_DIR)) {
135             $ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp';
136         } else {
137             // do not accept url based paths, eg. http:/ or ftp:/
138             if (strpos($ADODB_CACHE_DIR,'://') !== false)
139                 die("Illegal path http:// or ftp://");
140         }
141
142
143         // Initialize random number generator for randomizing cache flushes
144         srand(((double)microtime())*1000000);
145
146         /**
147          * ADODB version as a string.
148          */
149         $ADODB_vers = 'V4.22 15 Apr 2004 (c) 2000-2004 John Lim (jlim#natsoft.com.my). All rights reserved. Released BSD & LGPL.';
150
151         /**
152          * Determines whether recordset->RecordCount() is used.
153          * Set to false for highest performance -- RecordCount() will always return -1 then
154          * for databases that provide "virtual" recordcounts...
155          */
156         if (!isset($ADODB_COUNTRECS)) $ADODB_COUNTRECS = true;
157     }
158
159
160     //==========================================================================
161     // CHANGE NOTHING BELOW UNLESS YOU ARE DESIGNING ADODB
162     //==========================================================================
163
164     ADODB_Setup();
165
166     //==========================================================================
167     // CLASS ADOFieldObject
168     //==========================================================================
169     /**
170      * Helper class for FetchFields -- holds info on a column
171      */
172     class ADOFieldObject {
173         var $name = '';
174         var $max_length=0;
175         var $type="";
176
177         // additional fields by dannym... (danny_milo@yahoo.com)
178         var $not_null = false;
179         // actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^
180         // so we can as well make not_null standard (leaving it at "false" does not harm anyways)
181
182         var $has_default = false; // this one I have done only in mysql and postgres for now ...
183             // others to come (dannym)
184         var $default_value; // default, if any, and supported. Check has_default first.
185     }
186
187
188
189     function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection)
190     {
191         //print "Errorno ($fn errno=$errno m=$errmsg) ";
192         $thisConnection->_transOK = false;
193         if ($thisConnection->_oldRaiseFn) {
194             $fn = $thisConnection->_oldRaiseFn;
195             $fn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection);
196         }
197     }
198
199     //==========================================================================
200     // CLASS ADOConnection
201     //==========================================================================
202
203     /**
204      * Connection object. For connecting to databases, and executing queries.
205      */
206     class ADOConnection {
207     //
208     // PUBLIC VARS
209     //
210     var $dataProvider = 'native';
211     var $databaseType = '';        /// RDBMS currently in use, eg. odbc, mysql, mssql
212     var $database = '';            /// Name of database to be used.
213     var $host = '';             /// The hostname of the database server
214     var $user = '';             /// The username which is used to connect to the database server.
215     var $password = '';         /// Password for the username. For security, we no longer store it.
216     var $debug = false;         /// if set to true will output sql statements
217     var $maxblobsize = 256000;     /// maximum size of blobs or large text fields -- some databases die otherwise like foxpro
218     var $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase
219     var $substr = 'substr';        /// substring operator
220     var $length = 'length';        /// string length operator
221     var $random = 'rand()';        /// random function
222     var $upperCase = false;        /// uppercase function
223     var $fmtDate = "'Y-m-d'";    /// used by DBDate() as the default date format used by the database
224     var $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /// used by DBTimeStamp as the default timestamp fmt.
225     var $true = '1';             /// string that represents TRUE for a database
226     var $false = '0';             /// string that represents FALSE for a database
227     var $replaceQuote = "\\'";     /// string to use to replace quotes
228     var $nameQuote = '"';        /// string to use to quote identifiers and names
229     var $charSet=false;         /// character set to use - only for interbase
230     var $metaDatabasesSQL = '';
231     var $metaTablesSQL = '';
232     var $uniqueOrderBy = false; /// All order by columns have to be unique
233     var $emptyDate = '&nbsp;';
234     var $emptyTimeStamp = '&nbsp;';
235     var $lastInsID = false;
236     //--
237     var $hasInsertID = false;         /// supports autoincrement ID?
238     var $hasAffectedRows = false;     /// supports affected rows for update/delete?
239     var $hasTop = false;            /// support mssql/access SELECT TOP 10 * FROM TABLE
240     var $hasLimit = false;            /// support pgsql/mysql SELECT * FROM TABLE LIMIT 10
241     var $readOnly = false;             /// this is a readonly database - used by phpLens
242     var $hasMoveFirst = false;  /// has ability to run MoveFirst(), scrolling backwards
243     var $hasGenID = false;         /// can generate sequences using GenID();
244     var $hasTransactions = true; /// has transactions
245     //--
246     var $genID = 0;             /// sequence id used by GenID();
247     var $raiseErrorFn = false;     /// error function to call
248     var $isoDates = false; /// accepts dates in ISO format
249     var $cacheSecs = 3600; /// cache for 1 hour
250     var $sysDate = false; /// name of function that returns the current date
251     var $sysTimeStamp = false; /// name of function that returns the current timestamp
252     var $arrayClass = 'ADORecordSet_array'; /// name of class used to generate array recordsets, which are pre-downloaded recordsets
253
254     var $noNullStrings = false; /// oracle specific stuff - if true ensures that '' is converted to ' '
255     var $numCacheHits = 0;
256     var $numCacheMisses = 0;
257     var $pageExecuteCountRows = true;
258     var $uniqueSort = false; /// indicates that all fields in order by must be unique
259     var $leftOuter = false; /// operator to use for left outer join in WHERE clause
260     var $rightOuter = false; /// operator to use for right outer join in WHERE clause
261     var $ansiOuter = false; /// whether ansi outer join syntax supported
262     var $autoRollback = false; // autoRollback on PConnect().
263     var $poorAffectedRows = false; // affectedRows not working or unreliable
264
265     var $fnExecute = false;
266     var $fnCacheExecute = false;
267     var $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char
268     var $rsPrefix = "ADORecordSet_";
269
270     var $autoCommit = true;     /// do not modify this yourself - actually private
271     var $transOff = 0;             /// temporarily disable transactions
272     var $transCnt = 0;             /// count of nested transactions
273
274     var $fetchMode=false;
275      //
276      // PRIVATE VARS
277      //
278     var $_oldRaiseFn =  false;
279     var $_transOK = null;
280     var $_connectionID    = false;    /// The returned link identifier whenever a successful database connection is made.
281     var $_errorMsg = false;        /// A variable which was used to keep the returned last error message.  The value will
282                                 /// then returned by the errorMsg() function
283     var $_errorCode = false;    /// Last error code, not guaranteed to be used - only by oci8
284     var $_queryID = false;        /// This variable keeps the last created result link identifier
285
286     var $_isPersistentConnection = false;    /// A boolean variable to state whether its a persistent connection or normal connection.    */
287     var $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters.
288     var $_evalAll = false;
289     var $_affected = false;
290     var $_logsql = false;
291
292
293
294     /**
295      * Constructor
296      */
297     function ADOConnection()
298     {
299         die('Virtual Class -- cannot instantiate');
300     }
301
302     /**
303         Get server version info...
304
305         @returns An array with 2 elements: $arr['string'] is the description string,
306             and $arr[version] is the version (also a string).
307     */
308     function ServerInfo()
309     {
310         return array('description' => '', 'version' => '');
311     }
312
313     function _findvers($str)
314     {
315         if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) return $arr[1];
316         else return '';
317     }
318
319     /**
320     * All error messages go through this bottleneck function.
321     * You can define your own handler by defining the function name in ADODB_OUTP.
322     */
323     function outp($msg,$newline=true)
324     {
325     global $HTTP_SERVER_VARS,$ADODB_FLUSH,$ADODB_OUTP;
326
327         if (defined('ADODB_OUTP')) {
328             $fn = ADODB_OUTP;
329             $fn($msg,$newline);
330             return;
331         } elseif (isset($ADODB_OUTP)) {
332             $fn = $ADODB_OUTP;
333             $fn($msg,$newline);
334             return;
335         }
336
337         if ($newline) $msg .= "<br>\n";
338
339         if (isset($HTTP_SERVER_VARS['HTTP_USER_AGENT'])) echo $msg;
340         else echo strip_tags($msg);
341         if (!empty($ADODB_FLUSH) && ob_get_length() !== false) flush(); //  dp not flush if output buffering enabled - useless - thx to Jesse Mullan
342
343     }
344
345     function Time()
346     {
347         $rs =& $this->Execute("select $this->sysTimeStamp");
348         if ($rs && !$rs->EOF) return $this->UnixTimeStamp(reset($rs->fields));
349
350         return false;
351     }
352
353     /**
354      * Connect to database
355      *
356      * @param [argHostname]        Host to connect to
357      * @param [argUsername]        Userid to login
358      * @param [argPassword]        Associated password
359      * @param [argDatabaseName]    database
360      * @param [forceNew]        force new connection
361      *
362      * @return true or false
363      */
364     function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false)
365     {
366         if ($argHostname != "") $this->host = $argHostname;
367         if ($argUsername != "") $this->user = $argUsername;
368         if ($argPassword != "") $this->password = $argPassword; // not stored for security reasons
369         if ($argDatabaseName != "") $this->database = $argDatabaseName;
370
371         $this->_isPersistentConnection = false;
372         if ($fn = $this->raiseErrorFn) {
373             if ($forceNew) {
374                 if ($this->_nconnect($this->host, $this->user, $this->password, $this->database)) return true;
375             } else {
376                  if ($this->_connect($this->host, $this->user, $this->password, $this->database)) return true;
377             }
378             $err = $this->ErrorMsg();
379             if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'";
380             $fn($this->databaseType,'CONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
381         } else {
382             if ($forceNew) {
383                 if ($this->_nconnect($this->host, $this->user, $this->password, $this->database)) return true;
384             } else {
385                 if ($this->_connect($this->host, $this->user, $this->password, $this->database)) return true;
386             }
387         }
388         if ($this->debug) ADOConnection::outp( $this->host.': '.$this->ErrorMsg());
389         return false;
390     }
391
392      function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName)
393      {
394          return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName);
395      }
396
397
398     /**
399      * Always force a new connection to database - currently only works with oracle
400      *
401      * @param [argHostname]        Host to connect to
402      * @param [argUsername]        Userid to login
403      * @param [argPassword]        Associated password
404      * @param [argDatabaseName]    database
405      *
406      * @return true or false
407      */
408     function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "")
409     {
410         return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true);
411     }
412
413     /**
414      * Establish persistent connect to database
415      *
416      * @param [argHostname]        Host to connect to
417      * @param [argUsername]        Userid to login
418      * @param [argPassword]        Associated password
419      * @param [argDatabaseName]    database
420      *
421      * @return return true or false
422      */
423     function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "")
424     {
425         if (defined('ADODB_NEVER_PERSIST'))
426             return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName);
427
428         if ($argHostname != "") $this->host = $argHostname;
429         if ($argUsername != "") $this->user = $argUsername;
430         if ($argPassword != "") $this->password = $argPassword;
431         if ($argDatabaseName != "") $this->database = $argDatabaseName;
432
433         $this->_isPersistentConnection = true;
434
435         if ($fn = $this->raiseErrorFn) {
436             if ($this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true;
437             $err = $this->ErrorMsg();
438             if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'";
439             $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this);
440         } else
441             if ($this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true;
442
443         if ($this->debug) ADOConnection::outp( $this->host.': '.$this->ErrorMsg());
444         return false;
445     }
446
447     // Format date column in sql string given an input format that understands Y M D
448     function SQLDate($fmt, $col=false)
449     {
450         if (!$col) $col = $this->sysDate;
451         return $col; // child class implement
452     }
453
454     /**
455      * Should prepare the sql statement and return the stmt resource.
456      * For databases that do not support this, we return the $sql. To ensure
457      * compatibility with databases that do not support prepare:
458      *
459      *   $stmt = $db->Prepare("insert into table (id, name) values (?,?)");
460      *   $db->Execute($stmt,array(1,'Jill')) or die('insert failed');
461      *   $db->Execute($stmt,array(2,'Joe')) or die('insert failed');
462      *
463      * @param sql    SQL to send to database
464      *
465      * @return return FALSE, or the prepared statement, or the original sql if
466      *             if the database does not support prepare.
467      *
468      */
469     function Prepare($sql)
470     {
471         return $sql;
472     }
473
474     /**
475      * Some databases, eg. mssql require a different function for preparing
476      * stored procedures. So we cannot use Prepare().
477      *
478      * Should prepare the stored procedure  and return the stmt resource.
479      * For databases that do not support this, we return the $sql. To ensure
480      * compatibility with databases that do not support prepare:
481      *
482      * @param sql    SQL to send to database
483      *
484      * @return return FALSE, or the prepared statement, or the original sql if
485      *             if the database does not support prepare.
486      *
487      */
488     function PrepareSP($sql,$param=true)
489     {
490         return $this->Prepare($sql,$param);
491     }
492
493     /**
494     * PEAR DB Compat
495     */
496     function Quote($s)
497     {
498         return $this->qstr($s,false);
499     }
500
501     /**
502      Requested by "Karsten Dambekalns" <k.dambekalns@fishfarm.de>
503     */
504     function QMagic($s)
505     {
506         return $this->qstr($s,get_magic_quotes_gpc());
507     }
508
509     function q(&$s)
510     {
511         $s = $this->qstr($s,false);
512     }
513
514     /**
515     * PEAR DB Compat - do not use internally.
516     */
517     function ErrorNative()
518     {
519         return $this->ErrorNo();
520     }
521
522
523    /**
524     * PEAR DB Compat - do not use internally.
525     */
526     function nextId($seq_name)
527     {
528         return $this->GenID($seq_name);
529     }
530
531     /**
532     *     Lock a row, will escalate and lock the table if row locking not supported
533     *    will normally free the lock at the end of the transaction
534     *
535     *  @param $table    name of table to lock
536     *  @param $where    where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock
537     */
538     function RowLock($table,$where)
539     {
540         return false;
541     }
542
543     function CommitLock($table)
544     {
545         return $this->CommitTrans();
546     }
547
548     function RollbackLock($table)
549     {
550         return $this->RollbackTrans();
551     }
552
553     /**
554     * PEAR DB Compat - do not use internally.
555     *
556     * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical
557     *     for easy porting :-)
558     *
559     * @param mode    The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM
560     * @returns        The previous fetch mode
561     */
562     function SetFetchMode($mode)
563     {
564         $old = $this->fetchMode;
565         $this->fetchMode = $mode;
566
567         if ($old === false) {
568         global $ADODB_FETCH_MODE;
569             return $ADODB_FETCH_MODE;
570         }
571         return $old;
572     }
573
574
575     /**
576     * PEAR DB Compat - do not use internally.
577     */
578     function &Query($sql, $inputarr=false)
579     {
580         $rs = &$this->Execute($sql, $inputarr);
581         if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error();
582         return $rs;
583     }
584
585
586     /**
587     * PEAR DB Compat - do not use internally
588     */
589     function &LimitQuery($sql, $offset, $count, $params=false)
590     {
591         $rs = &$this->SelectLimit($sql, $count, $offset, $params);
592         if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error();
593         return $rs;
594     }
595
596
597     /**
598     * PEAR DB Compat - do not use internally
599     */
600     function Disconnect()
601     {
602         return $this->Close();
603     }
604
605     /*
606          Returns placeholder for parameter, eg.
607          $DB->Param('a')
608
609          will return ':a' for Oracle, and '?' for most other databases...
610
611          For databases that require positioned params, eg $1, $2, $3 for postgresql,
612              pass in Param(false) before setting the first parameter.
613     */
614     function Param($name)
615     {
616         return '?';
617     }
618
619     /*
620         InParameter and OutParameter are self-documenting versions of Parameter().
621     */
622     function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false)
623     {
624         return $this->Parameter($stmt,$var,$name,false,$maxLen,$type);
625     }
626
627     /*
628     */
629     function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false)
630     {
631         return $this->Parameter($stmt,$var,$name,true,$maxLen,$type);
632
633     }
634
635     /*
636     Usage in oracle
637         $stmt = $db->Prepare('select * from table where id =:myid and group=:group');
638         $db->Parameter($stmt,$id,'myid');
639         $db->Parameter($stmt,$group,'group',64);
640         $db->Execute();
641
642         @param $stmt Statement returned by Prepare() or PrepareSP().
643         @param $var PHP variable to bind to
644         @param $name Name of stored procedure variable name to bind to.
645         @param [$isOutput] Indicates direction of parameter 0/false=IN  1=OUT  2= IN/OUT. This is ignored in oci8.
646         @param [$maxLen] Holds an maximum length of the variable.
647         @param [$type] The data type of $var. Legal values depend on driver.
648
649     */
650     function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false)
651     {
652         return false;
653     }
654
655     /**
656         Improved method of initiating a transaction. Used together with CompleteTrans().
657         Advantages include:
658
659         a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans.
660            Only the outermost block is treated as a transaction.<br>
661         b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br>
662         c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block
663            are disabled, making it backward compatible.
664     */
665     function StartTrans($errfn = 'ADODB_TransMonitor')
666     {
667         if ($this->transOff > 0) {
668             $this->transOff += 1;
669             return;
670         }
671
672         $this->_oldRaiseFn = $this->raiseErrorFn;
673         $this->raiseErrorFn = $errfn;
674         $this->_transOK = true;
675
676         if ($this->debug && $this->transCnt > 0) ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans");
677         $this->BeginTrans();
678         $this->transOff = 1;
679     }
680
681     /**
682         Used together with StartTrans() to end a transaction. Monitors connection
683         for sql errors, and will commit or rollback as appropriate.
684
685         @autoComplete if true, monitor sql errors and commit and rollback as appropriate,
686         and if set to false force rollback even if no SQL error detected.
687         @returns true on commit, false on rollback.
688     */
689     function CompleteTrans($autoComplete = true)
690     {
691         if ($this->transOff > 1) {
692             $this->transOff -= 1;
693             return true;
694         }
695         $this->raiseErrorFn = $this->_oldRaiseFn;
696
697         $this->transOff = 0;
698         if ($this->_transOK && $autoComplete) {
699             if (!$this->CommitTrans()) {
700                 $this->_transOK = false;
701                 if ($this->debug) ADOConnection::outp("Smart Commit failed");
702             } else
703                 if ($this->debug) ADOConnection::outp("Smart Commit occurred");
704         } else {
705             $this->RollbackTrans();
706             if ($this->debug) ADOCOnnection::outp("Smart Rollback occurred");
707         }
708
709         return $this->_transOK;
710     }
711
712     /*
713         At the end of a StartTrans/CompleteTrans block, perform a rollback.
714     */
715     function FailTrans()
716     {
717         if ($this->debug)
718             if ($this->transOff == 0) {
719                 ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans");
720             } else {
721                 ADOConnection::outp("FailTrans was called");
722                 adodb_backtrace();
723             }
724         $this->_transOK = false;
725     }
726
727     /**
728         Check if transaction has failed, only for Smart Transactions.
729     */
730     function HasFailedTrans()
731     {
732         if ($this->transOff > 0) return $this->_transOK == false;
733         return false;
734     }
735
736     /**
737      * Execute SQL
738      *
739      * @param sql        SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text)
740      * @param [inputarr]    holds the input data to bind to. Null elements will be set to null.
741      * @return RecordSet or false
742      */
743     function &Execute($sql,$inputarr=false)
744     {
745         if ($this->fnExecute) {
746             $fn = $this->fnExecute;
747             $ret =& $fn($this,$sql,$inputarr);
748             if (isset($ret)) return $ret;
749         }
750         if ($inputarr && is_array($inputarr)) {
751             $element0 = reset($inputarr);
752             # is_object check is because oci8 descriptors can be passed in
753             $array_2d = is_array($element0) && !is_object(reset($element0));
754
755             if (!is_array($sql) && !$this->_bindInputArray) {
756                 $sqlarr = explode('?',$sql);
757
758                 if (!$array_2d) $inputarr = array($inputarr);
759                 foreach($inputarr as $arr) {
760                     $sql = ''; $i = 0;
761                     foreach($arr as $v) {
762                         $sql .= $sqlarr[$i];
763                         // from Ron Baldwin <ron.baldwin@sourceprose.com>
764                         // Only quote string types
765                         if (gettype($v) == 'string')
766                             $sql .= $this->qstr($v);
767                         else if ($v === null)
768                             $sql .= 'NULL';
769                         else
770                             $sql .= $v;
771                         $i += 1;
772                     }
773                     $sql .= $sqlarr[$i];
774
775                     if ($i+1 != sizeof($sqlarr))
776                         ADOConnection::outp( "Input Array does not match ?: ".htmlspecialchars($sql));
777
778                     $ret =& $this->_Execute($sql,false);
779                     if (!$ret) return $ret;
780                 }
781             } else {
782                 if ($array_2d) {
783                     $stmt = $this->Prepare($sql);
784                     foreach($inputarr as $arr) {
785                         $ret =& $this->_Execute($stmt,$arr);
786                         if (!$ret) return $ret;
787                     }
788                 } else
789                     $ret =& $this->_Execute($sql,$inputarr);
790             }
791         } else {
792             $ret =& $this->_Execute($sql,false);
793         }
794
795         return $ret;
796     }
797
798     function& _Execute($sql,$inputarr=false)
799     {
800
801         if ($this->debug) {
802         global $HTTP_SERVER_VARS;
803
804             $ss = '';
805             if ($inputarr) {
806                 foreach($inputarr as $kk=>$vv) {
807                     if (is_string($vv) && strlen($vv)>64) $vv = substr($vv,0,64).'...';
808                     $ss .= "($kk=>'$vv') ";
809                 }
810                 $ss = "[ $ss ]";
811             }
812             $sqlTxt = str_replace(',',', ',is_array($sql) ?$sql[0] : $sql);
813
814             // check if running from browser or command-line
815             $inBrowser = isset($HTTP_SERVER_VARS['HTTP_USER_AGENT']);
816
817             if ($inBrowser) {
818                 if ($this->debug === -1)
819                     ADOConnection::outp( "<br>\n($this->databaseType): ".htmlspecialchars($sqlTxt)." &nbsp; <code>$ss</code>\n<br>\n",false);
820                 else
821                     ADOConnection::outp( "<hr>\n($this->databaseType): ".htmlspecialchars($sqlTxt)." &nbsp; <code>$ss</code>\n<hr>\n",false);
822             } else {
823                 ADOConnection::outp("-----\n($this->databaseType): ".($sqlTxt)." \n-----\n",false);
824             }
825             $this->_queryID = $this->_query($sql,$inputarr);
826             /*
827                 Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql
828                 because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion
829             */
830             if ($this->databaseType == 'mssql') {
831             // ErrorNo is a slow function call in mssql, and not reliable in PHP 4.0.6
832                 if($emsg = $this->ErrorMsg()) {
833                     if ($err = $this->ErrorNo()) ADOConnection::outp($err.': '.$emsg);
834                 }
835             } elseif (!$this->_queryID) {
836                 ADOConnection::outp($this->ErrorNo() .': '. $this->ErrorMsg());
837             }
838         } else {
839             //****************************
840             // non-debug version of query
841             //****************************
842
843             $this->_queryID =@$this->_query($sql,$inputarr);
844         }
845
846         /************************
847         // OK, query executed
848         *************************/
849
850         if ($this->_queryID === false) {
851         // error handling if query fails
852             if ($this->debug == 99) adodb_backtrace(true,5);
853             $fn = $this->raiseErrorFn;
854             if ($fn) {
855                 $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this);
856             }
857
858             return false;
859         }
860
861
862         if ($this->_queryID === true) {
863         // return simplified empty recordset for inserts/updates/deletes with lower overhead
864             $rs =& new ADORecordSet_empty();
865             return $rs;
866         }
867
868         // return real recordset from select statement
869         $rsclass = $this->rsPrefix.$this->databaseType;
870         $rs =& new $rsclass($this->_queryID,$this->fetchMode);
871         $rs->connection = &$this; // Pablo suggestion
872         $rs->Init();
873         if (is_array($sql)) $rs->sql = $sql[0];
874         else $rs->sql = $sql;
875         if ($rs->_numOfRows <= 0) {
876         global $ADODB_COUNTRECS;
877
878             if ($ADODB_COUNTRECS) {
879                 if (!$rs->EOF){
880                     $rs = &$this->_rs2rs($rs,-1,-1,!is_array($sql));
881                     $rs->_queryID = $this->_queryID;
882                 } else
883                     $rs->_numOfRows = 0;
884             }
885         }
886         return $rs;
887     }
888
889     function CreateSequence($seqname='adodbseq',$startID=1)
890     {
891         if (empty($this->_genSeqSQL)) return false;
892         return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
893     }
894
895     function DropSequence($seqname)
896     {
897         if (empty($this->_dropSeqSQL)) return false;
898         return $this->Execute(sprintf($this->_dropSeqSQL,$seqname));
899     }
900
901     /**
902      * Generates a sequence id and stores it in $this->genID;
903      * GenID is only available if $this->hasGenID = true;
904      *
905      * @param seqname        name of sequence to use
906      * @param startID        if sequence does not exist, start at this ID
907      * @return 0 if not supported, otherwise a sequence id
908      */
909     function GenID($seqname='adodbseq',$startID=1)
910     {
911         if (!$this->hasGenID) {
912             return 0; // formerly returns false pre 1.60
913         }
914
915         $getnext = sprintf($this->_genIDSQL,$seqname);
916
917         $holdtransOK = $this->_transOK;
918         $rs = @$this->Execute($getnext);
919         if (!$rs) {
920             $this->_transOK = $holdtransOK; //if the status was ok before reset
921             $createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID));
922             $rs = $this->Execute($getnext);
923         }
924         if ($rs && !$rs->EOF) $this->genID = reset($rs->fields);
925         else $this->genID = 0; // false
926
927         if ($rs) $rs->Close();
928
929         return $this->genID;
930     }
931
932     /**
933      * @return the last inserted ID. Not all databases support this.
934      */
935     function Insert_ID()
936     {
937         if ($this->_logsql && $this->lastInsID) return $this->lastInsID;
938         if ($this->hasInsertID) return $this->_insertid();
939         if ($this->debug) {
940             ADOConnection::outp( '<p>Insert_ID error</p>');
941             adodb_backtrace();
942         }
943         return false;
944     }
945
946
947     /**
948      * Portable Insert ID. Pablo Roca <pabloroca@mvps.org>
949      *
950      * @return the last inserted ID. All databases support this. But aware possible
951      * problems in multiuser environments. Heavy test this before deploying.
952      */
953     function PO_Insert_ID($table="", $id="")
954     {
955        if ($this->hasInsertID){
956            return $this->Insert_ID();
957        } else {
958            return $this->GetOne("SELECT MAX($id) FROM $table");
959        }
960     }
961
962     /**
963     * @return # rows affected by UPDATE/DELETE
964     */
965     function Affected_Rows()
966     {
967         if ($this->hasAffectedRows) {
968             if ($this->fnExecute === 'adodb_log_sql') {
969                 if ($this->_logsql && $this->_affected !== false) return $this->_affected;
970             }
971             $val = $this->_affectedrows();
972             return ($val < 0) ? false : $val;
973         }
974
975         if ($this->debug) ADOConnection::outp( '<p>Affected_Rows error</p>',false);
976         return false;
977     }
978
979
980     /**
981      * @return the last error message
982      */
983     function ErrorMsg()
984     {
985         return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg;
986     }
987
988
989     /**
990      * @return the last error number. Normally 0 means no error.
991      */
992     function ErrorNo()
993     {
994         return ($this->_errorMsg) ? -1 : 0;
995     }
996
997     function MetaError($err=false)
998     {
999         include_once(ADODB_DIR."/adodb-error.inc.php");
1000         if ($err === false) $err = $this->ErrorNo();
1001         return adodb_error($this->dataProvider,$this->databaseType,$err);
1002     }
1003
1004     function MetaErrorMsg($errno)
1005     {
1006         include_once(ADODB_DIR."/adodb-error.inc.php");
1007         return adodb_errormsg($errno);
1008     }
1009
1010     /**
1011      * @returns an array with the primary key columns in it.
1012      */
1013     function MetaPrimaryKeys($table, $owner=false)
1014     {
1015     // owner not used in base class - see oci8
1016         $p = array();
1017         $objs =& $this->MetaColumns($table);
1018         if ($objs) {
1019             foreach($objs as $v) {
1020                 if (!empty($v->primary_key))
1021                     $p[] = $v->name;
1022             }
1023         }
1024         if (sizeof($p)) return $p;
1025         if (function_exists('ADODB_VIEW_PRIMARYKEYS'))
1026             return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner);
1027         return false;
1028     }
1029
1030     /**
1031      * @returns assoc array where keys are tables, and values are foreign keys
1032      */
1033     function MetaForeignKeys($table, $owner=false, $upper=false)
1034     {
1035         return false;
1036     }
1037     /**
1038      * Choose a database to connect to. Many databases do not support this.
1039      *
1040      * @param dbName     is the name of the database to select
1041      * @return true or false
1042      */
1043     function SelectDB($dbName)
1044     {return false;}
1045
1046
1047     /**
1048     * Will select, getting rows from $offset (1-based), for $nrows.
1049     * This simulates the MySQL "select * from table limit $offset,$nrows" , and
1050     * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
1051     * MySQL and PostgreSQL parameter ordering is the opposite of the other.
1052     * eg.
1053     *  SelectLimit('select * from table',3); will return rows 1 to 3 (1-based)
1054     *  SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based)
1055     *
1056     * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set)
1057     * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set
1058     *
1059     * @param sql
1060     * @param [offset]    is the row to start calculations from (1-based)
1061     * @param [nrows]        is the number of rows to get
1062     * @param [inputarr]    array of bind variables
1063     * @param [secs2cache]        is a private parameter only used by jlim
1064     * @return        the recordset ($rs->databaseType == 'array')
1065      */
1066     function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0)
1067     {
1068         if ($this->hasTop && $nrows > 0) {
1069         // suggested by Reinhard Balling. Access requires top after distinct
1070          // Informix requires first before distinct - F Riosa
1071             $ismssql = (strpos($this->databaseType,'mssql') !== false);
1072             if ($ismssql) $isaccess = false;
1073             else $isaccess = (strpos($this->databaseType,'access') !== false);
1074
1075             if ($offset <= 0) {
1076
1077                     // access includes ties in result
1078                     if ($isaccess) {
1079                         $sql = preg_replace(
1080                         '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
1081
1082                         if ($secs2cache>0) {
1083                             $ret =& $this->CacheExecute($secs2cache, $sql,$inputarr);
1084                         } else {
1085                             $ret =& $this->Execute($sql,$inputarr);
1086                         }
1087                         return $ret; // PHP5 fix
1088                     } elseif ($ismssql){
1089                         $sql = preg_replace(
1090                         '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
1091                     } else {
1092                         $sql = preg_replace(
1093                         '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nrows.' ',$sql);
1094                     }
1095             } else {
1096                 $nn = $nrows + $offset;
1097                 if ($isaccess || $ismssql) {
1098                     $sql = preg_replace(
1099                     '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql);
1100                 } else {
1101                     $sql = preg_replace(
1102                     '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql);
1103                 }
1104             }
1105         }
1106
1107         // if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer  rows
1108         // 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS.
1109         global $ADODB_COUNTRECS;
1110
1111         $savec = $ADODB_COUNTRECS;
1112         $ADODB_COUNTRECS = false;
1113
1114         if ($offset>0){
1115             if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr);
1116             else $rs = &$this->Execute($sql,$inputarr);
1117         } else {
1118             if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr);
1119             else $rs = &$this->Execute($sql,$inputarr);
1120         }
1121         $ADODB_COUNTRECS = $savec;
1122         if ($rs && !$rs->EOF) {
1123             $rs =& $this->_rs2rs($rs,$nrows,$offset);
1124         }
1125         //print_r($rs);
1126         return $rs;
1127     }
1128
1129     /**
1130     * Create serializable recordset. Breaks rs link to connection.
1131     *
1132     * @param rs            the recordset to serialize
1133     */
1134     function &SerializableRS(&$rs)
1135     {
1136         $rs2 =& $this->_rs2rs($rs);
1137         $ignore = false;
1138         $rs2->connection =& $ignore;
1139
1140         return $rs2;
1141     }
1142
1143     /**
1144     * Convert database recordset to an array recordset
1145     * input recordset's cursor should be at beginning, and
1146     * old $rs will be closed.
1147     *
1148     * @param rs            the recordset to copy
1149     * @param [nrows]      number of rows to retrieve (optional)
1150     * @param [offset]     offset by number of rows (optional)
1151     * @return             the new recordset
1152     */
1153     function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true)
1154     {
1155         if (! $rs) return false;
1156
1157         $dbtype = $rs->databaseType;
1158         if (!$dbtype) {
1159             $rs = &$rs;  // required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ?
1160             return $rs;
1161         }
1162         if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) {
1163             $rs->MoveFirst();
1164             $rs = &$rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ?
1165             return $rs;
1166         }
1167         $flds = array();
1168         for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) {
1169             $flds[] = $rs->FetchField($i);
1170         }
1171         $arr =& $rs->GetArrayLimit($nrows,$offset);
1172         //print_r($arr);
1173         if ($close) $rs->Close();
1174
1175         $arrayClass = $this->arrayClass;
1176
1177         $rs2 =& new $arrayClass();
1178         $rs2->connection = &$this;
1179         $rs2->sql = $rs->sql;
1180         $rs2->dataProvider = $this->dataProvider;
1181         $rs2->InitArrayFields($arr,$flds);
1182         return $rs2;
1183     }
1184
1185     /*
1186     * Return all rows. Compat with PEAR DB
1187     */
1188     function &GetAll($sql, $inputarr=false)
1189     {
1190         $arr =& $this->GetArray($sql,$inputarr);
1191         return $arr;
1192     }
1193
1194     function &GetAssoc($sql, $inputarr=false,$force_array = false, $first2cols = false)
1195     {
1196         $rs =& $this->Execute($sql, $inputarr);
1197         if (!$rs) return false;
1198
1199         $arr =& $rs->GetAssoc($force_array,$first2cols);
1200         return $arr;
1201     }
1202
1203     function &CacheGetAssoc($secs2cache, $sql=false, $inputarr=false,$force_array = false, $first2cols = false)
1204     {
1205         if (!is_numeric($secs2cache)) {
1206             $first2cols = $force_array;
1207             $force_array = $inputarr;
1208         }
1209         $rs =& $this->CacheExecute($secs2cache, $sql, $inputarr);
1210         if (!$rs) return false;
1211
1212         $arr =& $rs->GetAssoc($force_array,$first2cols);
1213         return $arr;
1214     }
1215
1216     /**
1217     * Return first element of first row of sql statement. Recordset is disposed
1218     * for you.
1219     *
1220     * @param sql            SQL statement
1221     * @param [inputarr]        input bind array
1222     */
1223     function GetOne($sql,$inputarr=false)
1224     {
1225     global $ADODB_COUNTRECS;
1226         $crecs = $ADODB_COUNTRECS;
1227         $ADODB_COUNTRECS = false;
1228
1229         $ret = false;
1230         $rs = &$this->Execute($sql,$inputarr);
1231         if ($rs) {
1232             if (!$rs->EOF) $ret = reset($rs->fields);
1233             $rs->Close();
1234         }
1235         $ADODB_COUNTRECS = $crecs;
1236         return $ret;
1237     }
1238
1239     function CacheGetOne($secs2cache,$sql=false,$inputarr=false)
1240     {
1241         $ret = false;
1242         $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr);
1243         if ($rs) {
1244             if (!$rs->EOF) $ret = reset($rs->fields);
1245             $rs->Close();
1246         }
1247
1248         return $ret;
1249     }
1250
1251     function GetCol($sql, $inputarr = false, $trim = false)
1252     {
1253           $rv = false;
1254           $rs = &$this->Execute($sql, $inputarr);
1255           if ($rs) {
1256             $rv = array();
1257                if ($trim) {
1258                 while (!$rs->EOF) {
1259                     $rv[] = trim(reset($rs->fields));
1260                     $rs->MoveNext();
1261                    }
1262             } else {
1263                 while (!$rs->EOF) {
1264                     $rv[] = reset($rs->fields);
1265                     $rs->MoveNext();
1266                    }
1267             }
1268                $rs->Close();
1269           }
1270           return $rv;
1271     }
1272
1273     function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false)
1274     {
1275           $rv = false;
1276           $rs = &$this->CacheExecute($secs, $sql, $inputarr);
1277           if ($rs) {
1278             if ($trim) {
1279                 while (!$rs->EOF) {
1280                     $rv[] = trim(reset($rs->fields));
1281                     $rs->MoveNext();
1282                    }
1283             } else {
1284                 while (!$rs->EOF) {
1285                     $rv[] = reset($rs->fields);
1286                     $rs->MoveNext();
1287                    }
1288             }
1289                $rs->Close();
1290           }
1291           return $rv;
1292     }
1293
1294     /*
1295         Calculate the offset of a date for a particular database and generate
1296             appropriate SQL. Useful for calculating future/past dates and storing
1297             in a database.
1298
1299         If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour.
1300     */
1301     function OffsetDate($dayFraction,$date=false)
1302     {
1303         if (!$date) $date = $this->sysDate;
1304         return  '('.$date.'+'.$dayFraction.')';
1305     }
1306
1307
1308     /**
1309     *
1310     * @param sql            SQL statement
1311     * @param [inputarr]        input bind array
1312     */
1313     function &GetArray($sql,$inputarr=false)
1314     {
1315     global $ADODB_COUNTRECS;
1316
1317         $savec = $ADODB_COUNTRECS;
1318         $ADODB_COUNTRECS = false;
1319         $rs =& $this->Execute($sql,$inputarr);
1320         $ADODB_COUNTRECS = $savec;
1321         if (!$rs)
1322             if (defined('ADODB_PEAR')) return ADODB_PEAR_Error();
1323             else return false;
1324         $arr =& $rs->GetArray();
1325         $rs->Close();
1326         return $arr;
1327     }
1328
1329     function &CacheGetAll($secs2cache,$sql=false,$inputarr=false)
1330     {
1331     global $ADODB_COUNTRECS;
1332
1333         $savec = $ADODB_COUNTRECS;
1334         $ADODB_COUNTRECS = false;
1335         $rs =& $this->CacheExecute($secs2cache,$sql,$inputarr);
1336         $ADODB_COUNTRECS = $savec;
1337
1338         if (!$rs)
1339             if (defined('ADODB_PEAR')) return ADODB_PEAR_Error();
1340             else return false;
1341
1342         $arr =& $rs->GetArray();
1343         $rs->Close();
1344         return $arr;
1345     }
1346
1347
1348
1349     /**
1350     * Return one row of sql statement. Recordset is disposed for you.
1351     *
1352     * @param sql            SQL statement
1353     * @param [inputarr]        input bind array
1354     */
1355     function &GetRow($sql,$inputarr=false)
1356     {
1357     global $ADODB_COUNTRECS;
1358         $crecs = $ADODB_COUNTRECS;
1359         $ADODB_COUNTRECS = false;
1360
1361         $rs =& $this->Execute($sql,$inputarr);
1362
1363         $ADODB_COUNTRECS = $crecs;
1364         if ($rs) {
1365             if (!$rs->EOF) $arr = $rs->fields;
1366             else $arr = array();
1367             $rs->Close();
1368             return $arr;
1369         }
1370
1371         return false;
1372     }
1373
1374     function &CacheGetRow($secs2cache,$sql=false,$inputarr=false)
1375     {
1376         $rs =& $this->CacheExecute($secs2cache,$sql,$inputarr);
1377         if ($rs) {
1378             $arr = false;
1379             if (!$rs->EOF) $arr = $rs->fields;
1380             $rs->Close();
1381             return $arr;
1382         }
1383         return false;
1384     }
1385
1386     /**
1387     * Insert or replace a single record. Note: this is not the same as MySQL's replace.
1388     * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL.
1389     * Also note that no table locking is done currently, so it is possible that the
1390     * record be inserted twice by two programs...
1391     *
1392     * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname');
1393     *
1394     * $table        table name
1395     * $fieldArray    associative array of data (you must quote strings yourself).
1396     * $keyCol        the primary key field name or if compound key, array of field names
1397     * autoQuote        set to true to use a hueristic to quote strings. Works with nulls and numbers
1398     *                    but does not work with dates nor SQL functions.
1399     * has_autoinc    the primary key is an auto-inc field, so skip in insert.
1400     *
1401     * Currently blob replace not supported
1402     *
1403     * returns 0 = fail, 1 = update, 2 = insert
1404     */
1405
1406     function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false)
1407     {
1408         global $ADODB_INCLUDED_LIB;
1409         if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php');
1410
1411         return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc);
1412     }
1413
1414
1415     /**
1416     * Will select, getting rows from $offset (1-based), for $nrows.
1417     * This simulates the MySQL "select * from table limit $offset,$nrows" , and
1418     * the PostgreSQL "select * from table limit $nrows offset $offset". Note that
1419     * MySQL and PostgreSQL parameter ordering is the opposite of the other.
1420     * eg.
1421     *  CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based)
1422     *  CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based)
1423     *
1424     * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set
1425     *
1426     * @param [secs2cache]    seconds to cache data, set to 0 to force query. This is optional
1427     * @param sql
1428     * @param [offset]    is the row to start calculations from (1-based)
1429     * @param [nrows]    is the number of rows to get
1430     * @param [inputarr]    array of bind variables
1431     * @return        the recordset ($rs->databaseType == 'array')
1432      */
1433     function &CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false)
1434     {
1435         if (!is_numeric($secs2cache)) {
1436             if ($sql === false) $sql = -1;
1437             if ($offset == -1) $offset = false;
1438                                       // sql,    nrows, offset,inputarr
1439             $rs =& $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$this->cacheSecs);
1440         } else {
1441             if ($sql === false) ADOConnection::outp( "Warning: \$sql missing from CacheSelectLimit()");
1442             $rs =& $this->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache);
1443         }
1444         return $rs;
1445     }
1446
1447     /**
1448     * Flush cached recordsets that match a particular $sql statement.
1449     * If $sql == false, then we purge all files in the cache.
1450      */
1451     function CacheFlush($sql=false,$inputarr=false)
1452     {
1453     global $ADODB_CACHE_DIR;
1454
1455         if (strlen($ADODB_CACHE_DIR) > 1 && !$sql) {
1456             if (strncmp(PHP_OS,'WIN',3) === 0) {
1457                 $cmd = 'del /s '.str_replace('/','\\',$ADODB_CACHE_DIR).'\adodb_*.cache';
1458             } else {
1459                 $cmd = 'rm -rf '.$ADODB_CACHE_DIR.'/??/adodb_*.cache';
1460                 // old version 'rm -f `find '.$ADODB_CACHE_DIR.' -name adodb_*.cache`';
1461             }
1462             if ($this->debug) {
1463                 ADOConnection::outp( "CacheFlush: $cmd<br><pre>\n", system($cmd),"</pre>");
1464             } else {
1465                 exec($cmd);
1466             }
1467             return;
1468         }
1469         $f = $this->_gencachename($sql.serialize($inputarr),false);
1470         adodb_write_file($f,''); // is adodb_write_file needed?
1471         if (!@unlink($f)) {
1472             if ($this->debug) ADOConnection::outp( "CacheFlush: failed for $f");
1473         }
1474     }
1475
1476     /**
1477     * Private function to generate filename for caching.
1478     * Filename is generated based on:
1479     *
1480     *  - sql statement
1481     *  - database type (oci8, ibase, ifx, etc)
1482     *  - database name
1483     *  - userid
1484     *
1485     * We create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR).
1486     * Assuming that we can have 50,000 files per directory with good performance,
1487     * then we can scale to 12.8 million unique cached recordsets. Wow!
1488      */
1489     function _gencachename($sql,$createdir)
1490     {
1491     global $ADODB_CACHE_DIR;
1492
1493         $m = md5($sql.$this->databaseType.$this->database.$this->user);
1494         $dir = $ADODB_CACHE_DIR.'/'.substr($m,0,2);
1495         if ($createdir && !file_exists($dir)) {
1496             $oldu = umask(0);
1497             if (!mkdir($dir,0771))
1498                 if ($this->debug) ADOConnection::outp( "Unable to mkdir $dir for $sql");
1499             umask($oldu);
1500         }
1501         return $dir.'/adodb_'.$m.'.cache';
1502     }
1503
1504
1505     /**
1506      * Execute SQL, caching recordsets.
1507      *
1508      * @param [secs2cache]    seconds to cache data, set to 0 to force query.
1509      *                      This is an optional parameter.
1510      * @param sql        SQL statement to execute
1511      * @param [inputarr]    holds the input data  to bind to
1512      * @return RecordSet or false
1513      */
1514     function &CacheExecute($secs2cache,$sql=false,$inputarr=false)
1515     {
1516         if (!is_numeric($secs2cache)) {
1517             $inputarr = $sql;
1518             $sql = $secs2cache;
1519             $secs2cache = $this->cacheSecs;
1520         }
1521         global $ADODB_INCLUDED_CSV;
1522         if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php');
1523
1524         if (is_array($sql)) $sql = $sql[0];
1525
1526         $md5file = $this->_gencachename($sql.serialize($inputarr),true);
1527         $err = '';
1528
1529         if ($secs2cache > 0){
1530             $rs = &csv2rs($md5file,$err,$secs2cache);
1531             $this->numCacheHits += 1;
1532         } else {
1533             $err='Timeout 1';
1534             $rs = false;
1535             $this->numCacheMisses += 1;
1536         }
1537         if (!$rs) {
1538         // no cached rs found
1539             if ($this->debug) {
1540                 if (get_magic_quotes_runtime()) {
1541                     ADOConnection::outp("Please disable magic_quotes_runtime - it corrupts cache files :(");
1542                 }
1543                 if ($this->debug !== -1) ADOConnection::outp( " $md5file cache failure: $err (see sql below)");
1544             }
1545             $rs = &$this->Execute($sql,$inputarr);
1546             if ($rs) {
1547                 $eof = $rs->EOF;
1548                 $rs = &$this->_rs2rs($rs); // read entire recordset into memory immediately
1549                 $txt = _rs2serialize($rs,false,$sql); // serialize
1550
1551                 if (!adodb_write_file($md5file,$txt,$this->debug)) {
1552                     if ($fn = $this->raiseErrorFn) {
1553                         $fn($this->databaseType,'CacheExecute',-32000,"Cache write error",$md5file,$sql,$this);
1554                     }
1555                     if ($this->debug) ADOConnection::outp( " Cache write error");
1556                 }
1557                 if ($rs->EOF && !$eof) {
1558                     $rs->MoveFirst();
1559                     //$rs = &csv2rs($md5file,$err);
1560                     $rs->connection = &$this; // Pablo suggestion
1561                 }
1562
1563             } else
1564                 @unlink($md5file);
1565         } else {
1566             $this->_errorMsg = '';
1567             $this->_errorCode = 0;
1568
1569             if ($this->fnCacheExecute) {
1570                 $fn = $this->fnCacheExecute;
1571                 $fn($this, $secs2cache, $sql, $inputarr);
1572             }
1573         // ok, set cached object found
1574             $rs->connection = &$this; // Pablo suggestion
1575             if ($this->debug){
1576             global $HTTP_SERVER_VARS;
1577
1578                 $inBrowser = isset($HTTP_SERVER_VARS['HTTP_USER_AGENT']);
1579                 $ttl = $rs->timeCreated + $secs2cache - time();
1580                 $s = is_array($sql) ? $sql[0] : $sql;
1581                 if ($inBrowser) $s = '<i>'.htmlspecialchars($s).'</i>';
1582
1583                 ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]");
1584             }
1585         }
1586         return $rs;
1587     }
1588
1589
1590     /**
1591      * Generates an Update Query based on an existing recordset.
1592      * $arrFields is an associative array of fields with the value
1593      * that should be assigned.
1594      *
1595      * Note: This function should only be used on a recordset
1596      *       that is run against a single table and sql should only
1597      *         be a simple select stmt with no groupby/orderby/limit
1598      *
1599      * "Jonathan Younger" <jyounger@unilab.com>
1600        */
1601     function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false)
1602     {
1603         global $ADODB_INCLUDED_LIB;
1604         if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php');
1605         return _adodb_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq);
1606     }
1607
1608
1609     /**
1610      * Generates an Insert Query based on an existing recordset.
1611      * $arrFields is an associative array of fields with the value
1612      * that should be assigned.
1613      *
1614      * Note: This function should only be used on a recordset
1615      *       that is run against a single table.
1616        */
1617     function GetInsertSQL(&$rs, $arrFields,$magicq=false)
1618     {
1619         global $ADODB_INCLUDED_LIB;
1620         if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php');
1621         return _adodb_getinsertsql($this,$rs,$arrFields,$magicq);
1622     }
1623
1624
1625     /**
1626     * Update a blob column, given a where clause. There are more sophisticated
1627     * blob handling functions that we could have implemented, but all require
1628     * a very complex API. Instead we have chosen something that is extremely
1629     * simple to understand and use.
1630     *
1631     * Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course.
1632     *
1633     * Usage to update a $blobvalue which has a primary key blob_id=1 into a
1634     * field blobtable.blobcolumn:
1635     *
1636     *    UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1');
1637     *
1638     * Insert example:
1639     *
1640     *    $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
1641     *    $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1');
1642     */
1643
1644     function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB')
1645     {
1646         return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false;
1647     }
1648
1649     /**
1650     * Usage:
1651     *    UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1');
1652     *
1653     *    $blobtype supports 'BLOB' and 'CLOB'
1654     *
1655     *    $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)');
1656     *    $conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1');
1657     */
1658     function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB')
1659     {
1660         $fd = fopen($path,'rb');
1661         if ($fd === false) return false;
1662         $val = fread($fd,filesize($path));
1663         fclose($fd);
1664         return $this->UpdateBlob($table,$column,$val,$where,$blobtype);
1665     }
1666
1667     function BlobDecode($blob)
1668     {
1669         return $blob;
1670     }
1671
1672     function BlobEncode($blob)
1673     {
1674         return $blob;
1675     }
1676
1677     function SetCharSet($charset)
1678     {
1679         return false;
1680     }
1681
1682     function IfNull( $field, $ifNull )
1683     {
1684         return " CASE WHEN $field is null THEN $ifNull ELSE $field END ";
1685     }
1686
1687     function LogSQL($enable=true)
1688     {
1689         include_once(ADODB_DIR.'/adodb-perf.inc.php');
1690
1691         if ($enable) $this->fnExecute = 'adodb_log_sql';
1692         else $this->fnExecute = false;
1693
1694         $old = $this->_logsql;
1695         $this->_logsql = $enable;
1696         if ($enable && !$old) $this->_affected = false;
1697         return $old;
1698     }
1699
1700     function GetCharSet()
1701     {
1702         return false;
1703     }
1704
1705     /**
1706     * Usage:
1707     *    UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB');
1708     *
1709     *    $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)');
1710     *    $conn->UpdateClob('clobtable','clobcol',$clob,'id=1');
1711     */
1712     function UpdateClob($table,$column,$val,$where)
1713     {
1714         return $this->UpdateBlob($table,$column,$val,$where,'CLOB');
1715     }
1716
1717
1718     /**
1719     *  Change the SQL connection locale to a specified locale.
1720     *  This is used to get the date formats written depending on the client locale.
1721     */
1722     function SetDateLocale($locale = 'En')
1723     {
1724         $this->locale = $locale;
1725         switch ($locale)
1726         {
1727             default:
1728             case 'En':
1729                 $this->fmtDate="Y-m-d";
1730                 $this->fmtTimeStamp = "Y-m-d H:i:s";
1731                 break;
1732
1733             case 'Fr':
1734             case 'Ro':
1735             case 'It':
1736                 $this->fmtDate="d-m-Y";
1737                 $this->fmtTimeStamp = "d-m-Y H:i:s";
1738                 break;
1739
1740             case 'Ge':
1741                 $this->fmtDate="d.m.Y";
1742                 $this->fmtTimeStamp = "d.m.Y H:i:s";
1743                 break;
1744         }
1745     }
1746
1747
1748     /**
1749      *  $meta    contains the desired type, which could be...
1750      *    C for character. You will have to define the precision yourself.
1751      *    X for teXt. For unlimited character lengths.
1752      *    B for Binary
1753      *  F for floating point, with no need to define scale and precision
1754      *     N for decimal numbers, you will have to define the (scale, precision) yourself
1755      *    D for date
1756      *    T for timestamp
1757      *     L for logical/Boolean
1758      *    I for integer
1759      *    R for autoincrement counter/integer
1760      *  and if you want to use double-byte, add a 2 to the end, like C2 or X2.
1761      *
1762      *
1763      * @return the actual type of the data or false if no such type available
1764     */
1765      function ActualType($meta)
1766     {
1767         switch($meta) {
1768         case 'C':
1769         case 'X':
1770             return 'VARCHAR';
1771         case 'B':
1772
1773         case 'D':
1774         case 'T':
1775         case 'L':
1776
1777         case 'R':
1778
1779         case 'I':
1780         case 'N':
1781             return false;
1782         }
1783     }
1784
1785
1786     /**
1787      * Close Connection
1788      */
1789     function Close()
1790     {
1791         return $this->_close();
1792
1793         // "Simon Lee" <simon@mediaroad.com> reports that persistent connections need
1794         // to be closed too!
1795         //if ($this->_isPersistentConnection != true) return $this->_close();
1796         //else return true;
1797     }
1798
1799     /**
1800      * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans().
1801      *
1802      * @return true if succeeded or false if database does not support transactions
1803      */
1804     function BeginTrans() {return false;}
1805
1806
1807     /**
1808      * If database does not support transactions, always return true as data always commited
1809      *
1810      * @param $ok  set to false to rollback transaction, true to commit
1811      *
1812      * @return true/false.
1813      */
1814     function CommitTrans($ok=true)
1815     { return true;}
1816
1817
1818     /**
1819      * If database does not support transactions, rollbacks always fail, so return false
1820      *
1821      * @return true/false.
1822      */
1823     function RollbackTrans()
1824     { return false;}
1825
1826
1827     /**
1828      * return the databases that the driver can connect to.
1829      * Some databases will return an empty array.
1830      *
1831      * @return an array of database names.
1832      */
1833         function MetaDatabases()
1834         {
1835         global $ADODB_FETCH_MODE;
1836
1837             if ($this->metaDatabasesSQL) {
1838                 $save = $ADODB_FETCH_MODE;
1839                 $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
1840
1841                 if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
1842
1843                 $arr = $this->GetCol($this->metaDatabasesSQL);
1844                 if (isset($savem)) $this->SetFetchMode($savem);
1845                 $ADODB_FETCH_MODE = $save;
1846
1847                 return $arr;
1848             }
1849
1850             return false;
1851         }
1852
1853     /**
1854      * @param ttype can either be 'VIEW' or 'TABLE' or false.
1855      *         If false, both views and tables are returned.
1856      *        "VIEW" returns only views
1857      *        "TABLE" returns only tables
1858      * @param showSchema returns the schema/user with the table name, eg. USER.TABLE
1859      * @param mask  is the input mask - only supported by oci8 and postgresql
1860      *
1861      * @return array of tables for current database.
1862      */
1863     function &MetaTables($ttype=false,$showSchema=false,$mask=false)
1864     {
1865     global $ADODB_FETCH_MODE;
1866
1867         if ($mask) return false;
1868
1869         if ($this->metaTablesSQL) {
1870             // complicated state saving by the need for backward compat
1871             $save = $ADODB_FETCH_MODE;
1872             $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
1873
1874             if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
1875
1876             $rs = $this->Execute($this->metaTablesSQL);
1877             if (isset($savem)) $this->SetFetchMode($savem);
1878             $ADODB_FETCH_MODE = $save;
1879
1880             if ($rs === false) return false;
1881             $arr =& $rs->GetArray();
1882             $arr2 = array();
1883
1884             if ($hast = ($ttype && isset($arr[0][1]))) {
1885                 $showt = strncmp($ttype,'T',1);
1886             }
1887
1888             for ($i=0; $i < sizeof($arr); $i++) {
1889                 if ($hast) {
1890                     if ($showt == 0) {
1891                         if (strncmp($arr[$i][1],'T',1) == 0) $arr2[] = trim($arr[$i][0]);
1892                     } else {
1893                         if (strncmp($arr[$i][1],'V',1) == 0) $arr2[] = trim($arr[$i][0]);
1894                     }
1895                 } else
1896                     $arr2[] = trim($arr[$i][0]);
1897             }
1898             $rs->Close();
1899             return $arr2;
1900         }
1901         return false;
1902     }
1903
1904
1905     function _findschema(&$table,&$schema)
1906     {
1907         if (!$schema && ($at = strpos($table,'.')) !== false) {
1908             $schema = substr($table,0,$at);
1909             $table = substr($table,$at+1);
1910         }
1911     }
1912
1913     /**
1914      * List columns in a database as an array of ADOFieldObjects.
1915      * See top of file for definition of object.
1916      *
1917      * @param table    table name to query
1918      * @param upper    uppercase table name (required by some databases)
1919      * @schema is optional database schema to use - not supported by all databases.
1920      *
1921      * @return array of ADOFieldObjects for current table.
1922      */
1923     function &MetaColumns($table,$upper=true)
1924     {
1925     global $ADODB_FETCH_MODE;
1926
1927         if (!empty($this->metaColumnsSQL)) {
1928
1929             $schema = false;
1930             $this->_findschema($table,$schema);
1931
1932             $save = $ADODB_FETCH_MODE;
1933             $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
1934             if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false);
1935             $rs = $this->Execute(sprintf($this->metaColumnsSQL,($upper)?strtoupper($table):$table));
1936             if (isset($savem)) $this->SetFetchMode($savem);
1937             $ADODB_FETCH_MODE = $save;
1938             if ($rs === false) return false;
1939
1940             $retarr = array();
1941             while (!$rs->EOF) { //print_r($rs->fields);
1942                 $fld =& new ADOFieldObject();
1943                 $fld->name = $upper ? strtoupper($rs->fields[0]) : $rs->fields[0];
1944                 $fld->type = $rs->fields[1];
1945                 if (isset($rs->fields[3]) && $rs->fields[3]) {
1946                     if ($rs->fields[3]>0) $fld->max_length = $rs->fields[3];
1947                     $fld->scale = $rs->fields[4];
1948                     if ($fld->scale>0) $fld->max_length += 1;
1949                 } else
1950                     $fld->max_length = $rs->fields[2];
1951
1952                 if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld;
1953                 else $retarr[$fld->name] = $fld;
1954                 $rs->MoveNext();
1955             }
1956             $rs->Close();
1957             return $retarr;
1958         }
1959         return false;
1960     }
1961
1962     /**
1963       * List indexes on a table as an array.
1964       * @param table        table name to query
1965       * @param primary include primary keys.
1966       *
1967       * @return array of indexes on current table.
1968       */
1969      function &MetaIndexes($table, $primary = false, $owner = false)
1970      {
1971              return FALSE;
1972      }
1973
1974     /**
1975      * List columns names in a table as an array.
1976      * @param table    table name to query
1977      *
1978      * @return array of column names for current table.
1979      */
1980     function &MetaColumnNames($table)
1981     {
1982         $objarr =& $this->MetaColumns($table);
1983         if (!is_array($objarr)) return false;
1984
1985         $arr = array();
1986         foreach($objarr as $v) {
1987             $arr[strtoupper($v->name)] = $v->name;
1988         }
1989         return $arr;
1990     }
1991
1992     /**
1993      * Different SQL databases used different methods to combine strings together.
1994      * This function provides a wrapper.
1995      *
1996      * param s    variable number of string parameters
1997      *
1998      * Usage: $db->Concat($str1,$str2);
1999      *
2000      * @return concatenated string
2001      */
2002     function Concat()
2003     {
2004         $arr = func_get_args();
2005         return implode($this->concat_operator, $arr);
2006     }
2007
2008
2009     /**
2010      * Converts a date "d" to a string that the database can understand.
2011      *
2012      * @param d    a date in Unix date time format.
2013      *
2014      * @return date string in database date format
2015      */
2016     function DBDate($d)
2017     {
2018         if (empty($d) && $d !== 0) return 'null';
2019
2020         if (is_string($d) && !is_numeric($d)) {
2021             if ($d === 'null' || strncmp($d,"'",1) === 0) return $d;
2022             if ($this->isoDates) return "'$d'";
2023             $d = ADOConnection::UnixDate($d);
2024         }
2025
2026         return adodb_date($this->fmtDate,$d);
2027     }
2028
2029
2030     /**
2031      * Converts a timestamp "ts" to a string that the database can understand.
2032      *
2033      * @param ts    a timestamp in Unix date time format.
2034      *
2035      * @return timestamp string in database timestamp format
2036      */
2037     function DBTimeStamp($ts)
2038     {
2039         if (empty($ts) && $ts !== 0) return 'null';
2040
2041         # strlen(14) allows YYYYMMDDHHMMSS format
2042         if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14))
2043             return adodb_date($this->fmtTimeStamp,$ts);
2044
2045         if ($ts === 'null') return $ts;
2046         if ($this->isoDates && strlen($ts) !== 14) return "'$ts'";
2047
2048         $ts = ADOConnection::UnixTimeStamp($ts);
2049         return adodb_date($this->fmtTimeStamp,$ts);
2050     }
2051
2052     /**
2053      * Also in ADORecordSet.
2054      * @param $v is a date string in YYYY-MM-DD format
2055      *
2056      * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
2057      */
2058     function UnixDate($v)
2059     {
2060         if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|",
2061             ($v), $rr)) return false;
2062
2063         if ($rr[1] <= TIMESTAMP_FIRST_YEAR) return 0;
2064         // h-m-s-MM-DD-YY
2065         return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
2066     }
2067
2068
2069     /**
2070      * Also in ADORecordSet.
2071      * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
2072      *
2073      * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
2074      */
2075     function UnixTimeStamp($v)
2076     {
2077         if (!preg_match(
2078             "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
2079             ($v), $rr)) return false;
2080
2081         if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) return 0;
2082
2083         // h-m-s-MM-DD-YY
2084         if (!isset($rr[5])) return  adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
2085         return  @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]);
2086     }
2087
2088     /**
2089      * Also in ADORecordSet.
2090      *
2091      * Format database date based on user defined format.
2092      *
2093      * @param v      is the character date in YYYY-MM-DD format, returned by database
2094      * @param fmt     is the format to apply to it, using date()
2095      *
2096      * @return a date formated as user desires
2097      */
2098
2099     function UserDate($v,$fmt='Y-m-d')
2100     {
2101         $tt = $this->UnixDate($v);
2102         // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
2103         if (($tt === false || $tt == -1) && $v != false) return $v;
2104         else if ($tt == 0) return $this->emptyDate;
2105         else if ($tt == -1) { // pre-TIMESTAMP_FIRST_YEAR
2106         }
2107
2108         return adodb_date($fmt,$tt);
2109
2110     }
2111
2112         /**
2113      *
2114      * @param v      is the character timestamp in YYYY-MM-DD hh:mm:ss format
2115      * @param fmt     is the format to apply to it, using date()
2116      *
2117      * @return a timestamp formated as user desires
2118      */
2119     function UserTimeStamp($v,$fmt='Y-m-d H:i:s')
2120     {
2121         # strlen(14) allows YYYYMMDDHHMMSS format
2122         if (is_numeric($v) && strlen($v)<14) return adodb_date($fmt,$v);
2123         $tt = $this->UnixTimeStamp($v);
2124         // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
2125         if (($tt === false || $tt == -1) && $v != false) return $v;
2126         if ($tt == 0) return $this->emptyTimeStamp;
2127         return adodb_date($fmt,$tt);
2128     }
2129
2130     /**
2131     * Quotes a string, without prefixing nor appending quotes.
2132     */
2133     function addq($s, $magicq=false)
2134     {
2135         if (!$magicq) {
2136
2137             if ($this->replaceQuote[0] == '\\'){
2138                 // only since php 4.0.5
2139                 $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
2140                 //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
2141             }
2142             return  str_replace("'",$this->replaceQuote,$s);
2143         }
2144
2145         // undo magic quotes for "
2146         $s = str_replace('\\"','"',$s);
2147
2148         if ($this->replaceQuote == "\\'")  // ' already quoted, no need to change anything
2149             return $s;
2150         else {// change \' to '' for sybase/mssql
2151             $s = str_replace('\\\\','\\',$s);
2152             return str_replace("\\'",$this->replaceQuote,$s);
2153         }
2154     }
2155
2156     /**
2157      * Correctly quotes a string so that all strings are escaped. We prefix and append
2158      * to the string single-quotes.
2159      * An example is  $db->qstr("Don't bother",magic_quotes_runtime());
2160      *
2161      * @param s            the string to quote
2162      * @param [magic_quotes]    if $s is GET/POST var, set to get_magic_quotes_gpc().
2163      *                This undoes the stupidity of magic quotes for GPC.
2164      *
2165      * @return quoted string to be sent back to database
2166      */
2167     function qstr($s,$magic_quotes=false)
2168     {
2169         if (!$magic_quotes) {
2170
2171             if ($this->replaceQuote[0] == '\\'){
2172                 // only since php 4.0.5
2173                 $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
2174                 //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
2175             }
2176             return  "'".str_replace("'",$this->replaceQuote,$s)."'";
2177         }
2178
2179         // undo magic quotes for "
2180         $s = str_replace('\\"','"',$s);
2181
2182         if ($this->replaceQuote == "\\'")  // ' already quoted, no need to change anything
2183             return "'$s'";
2184         else {// change \' to '' for sybase/mssql
2185             $s = str_replace('\\\\','\\',$s);
2186             return "'".str_replace("\\'",$this->replaceQuote,$s)."'";
2187         }
2188     }
2189
2190
2191     /**
2192     * Will select the supplied $page number from a recordset, given that it is paginated in pages of
2193     * $nrows rows per page. It also saves two boolean values saying if the given page is the first
2194     * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
2195     *
2196     * See readme.htm#ex8 for an example of usage.
2197     *
2198     * @param sql
2199     * @param nrows        is the number of rows per page to get
2200     * @param page        is the page number to get (1-based)
2201     * @param [inputarr]    array of bind variables
2202     * @param [secs2cache]        is a private parameter only used by jlim
2203     * @return        the recordset ($rs->databaseType == 'array')
2204     *
2205     * NOTE: phpLens uses a different algorithm and does not use PageExecute().
2206     *
2207     */
2208     function &PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0)
2209     {
2210         global $ADODB_INCLUDED_LIB;
2211         if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php');
2212         if ($this->pageExecuteCountRows) return _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $secs2cache);
2213         return _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $secs2cache);
2214
2215     }
2216
2217
2218     /**
2219     * Will select the supplied $page number from a recordset, given that it is paginated in pages of
2220     * $nrows rows per page. It also saves two boolean values saying if the given page is the first
2221     * and/or last one of the recordset. Added by Iván Oliva to provide recordset pagination.
2222     *
2223     * @param secs2cache    seconds to cache data, set to 0 to force query
2224     * @param sql
2225     * @param nrows        is the number of rows per page to get
2226     * @param page        is the page number to get (1-based)
2227     * @param [inputarr]    array of bind variables
2228     * @return        the recordset ($rs->databaseType == 'array')
2229     */
2230     function &CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false)
2231     {
2232         /*switch($this->dataProvider) {
2233         case 'postgres':
2234         case 'mysql':
2235             break;
2236         default: $secs2cache = 0; break;
2237         }*/
2238         $rs =& $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache);
2239         return $rs;
2240     }
2241
2242 } // end class ADOConnection
2243
2244
2245
2246     //==============================================================================================
2247     // CLASS ADOFetchObj
2248     //==============================================================================================
2249
2250     /**
2251     * Internal placeholder for record objects. Used by ADORecordSet->FetchObj().
2252     */
2253     class ADOFetchObj {
2254     };
2255
2256     //==============================================================================================
2257     // CLASS ADORecordSet_empty
2258     //==============================================================================================
2259
2260     /**
2261     * Lightweight recordset when there are no records to be returned
2262     */
2263     class ADORecordSet_empty
2264     {
2265         var $dataProvider = 'empty';
2266         var $databaseType = false;
2267         var $EOF = true;
2268         var $_numOfRows = 0;
2269         var $fields = false;
2270         var $connection = false;
2271         function RowCount() {return 0;}
2272         function RecordCount() {return 0;}
2273         function PO_RecordCount(){return 0;}
2274         function Close(){return true;}
2275         function FetchRow() {return false;}
2276         function FieldCount(){ return 0;}
2277     }
2278
2279     //==============================================================================================
2280     // DATE AND TIME FUNCTIONS
2281     //==============================================================================================
2282     include_once(ADODB_DIR.'/adodb-time.inc.php');
2283
2284     //==============================================================================================
2285     // CLASS ADORecordSet
2286     //==============================================================================================
2287
2288     if (PHP_VERSION < 5) include_once(ADODB_DIR.'/adodb-php4.inc.php');
2289     else include_once(ADODB_DIR.'/adodb-iterator.inc.php');
2290    /**
2291      * RecordSet class that represents the dataset returned by the database.
2292      * To keep memory overhead low, this class holds only the current row in memory.
2293      * No prefetching of data is done, so the RecordCount() can return -1 ( which
2294      * means recordcount not known).
2295      */
2296     class ADORecordSet extends ADODB_BASE_RS {
2297     /*
2298      * public variables
2299      */
2300     var $dataProvider = "native";
2301     var $fields = false;     /// holds the current row data
2302     var $blobSize = 100;     /// any varchar/char field this size or greater is treated as a blob
2303                             /// in other words, we use a text area for editing.
2304     var $canSeek = false;     /// indicates that seek is supported
2305     var $sql;                 /// sql text
2306     var $EOF = false;        /// Indicates that the current record position is after the last record in a Recordset object.
2307
2308     var $emptyTimeStamp = '&nbsp;'; /// what to display when $time==0
2309     var $emptyDate = '&nbsp;'; /// what to display when $time==0
2310     var $debug = false;
2311     var $timeCreated=0;     /// datetime in Unix format rs created -- for cached recordsets
2312
2313     var $bind = false;         /// used by Fields() to hold array - should be private?
2314     var $fetchMode;            /// default fetch mode
2315     var $connection = false; /// the parent connection
2316     /*
2317      *    private variables
2318      */
2319     var $_numOfRows = -1;    /** number of rows, or -1 */
2320     var $_numOfFields = -1;    /** number of fields in recordset */
2321     var $_queryID = -1;        /** This variable keeps the result link identifier.    */
2322     var $_currentRow = -1;    /** This variable keeps the current row in the Recordset.    */
2323     var $_closed = false;     /** has recordset been closed */
2324     var $_inited = false;     /** Init() should only be called once */
2325     var $_obj;                 /** Used by FetchObj */
2326     var $_names;            /** Used by FetchObj */
2327
2328     var $_currentPage = -1;    /** Added by Iván Oliva to implement recordset pagination */
2329     var $_atFirstPage = false;    /** Added by Iván Oliva to implement recordset pagination */
2330     var $_atLastPage = false;    /** Added by Iván Oliva to implement recordset pagination */
2331     var $_lastPageNo = -1;
2332     var $_maxRecordCount = 0;
2333     var $datetime = false;
2334
2335     /**
2336      * Constructor
2337      *
2338      * @param queryID      this is the queryID returned by ADOConnection->_query()
2339      *
2340      */
2341     function ADORecordSet($queryID)
2342     {
2343         $this->_queryID = $queryID;
2344     }
2345
2346
2347
2348     function Init()
2349     {
2350         if ($this->_inited) return;
2351         $this->_inited = true;
2352         if ($this->_queryID) @$this->_initrs();
2353         else {
2354             $this->_numOfRows = 0;
2355             $this->_numOfFields = 0;
2356         }
2357         if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) {
2358
2359             $this->_currentRow = 0;
2360             if ($this->EOF = ($this->_fetch() === false)) {
2361                 $this->_numOfRows = 0; // _numOfRows could be -1
2362             }
2363         } else {
2364             $this->EOF = true;
2365         }
2366     }
2367
2368
2369     /**
2370      * Generate a SELECT tag string from a recordset, and return the string.
2371      * If the recordset has 2 cols, we treat the 1st col as the containing
2372      * the text to display to the user, and 2nd col as the return value. Default
2373      * strings are compared with the FIRST column.
2374      *
2375      * @param name          name of SELECT tag
2376      * @param [defstr]        the value to hilite. Use an array for multiple hilites for listbox.
2377      * @param [blank1stItem]    true to leave the 1st item in list empty
2378      * @param [multiple]        true for listbox, false for popup
2379      * @param [size]        #rows to show for listbox. not used by popup
2380      * @param [selectAttr]        additional attributes to defined for SELECT tag.
2381      *                useful for holding javascript onChange='...' handlers.
2382      & @param [compareFields0]    when we have 2 cols in recordset, we compare the defstr with
2383      *                column 0 (1st col) if this is true. This is not documented.
2384      *
2385      * @return HTML
2386      *
2387      * changes by glen.davies@cce.ac.nz to support multiple hilited items
2388      */
2389     function GetMenu($name,$defstr='',$blank1stItem=true,$multiple=false,
2390             $size=0, $selectAttr='',$compareFields0=true)
2391     {
2392         global $ADODB_INCLUDED_LIB;
2393         if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php');
2394         return _adodb_getmenu($this, $name,$defstr,$blank1stItem,$multiple,
2395             $size, $selectAttr,$compareFields0);
2396     }
2397
2398     /**
2399      * Generate a SELECT tag string from a recordset, and return the string.
2400      * If the recordset has 2 cols, we treat the 1st col as the containing
2401      * the text to display to the user, and 2nd col as the return value. Default
2402      * strings are compared with the SECOND column.
2403      *
2404      */
2405     function GetMenu2($name,$defstr='',$blank1stItem=true,$multiple=false,$size=0, $selectAttr='')
2406     {
2407         global $ADODB_INCLUDED_LIB;
2408         if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php');
2409         return _adodb_getmenu($this,$name,$defstr,$blank1stItem,$multiple,
2410             $size, $selectAttr,false);
2411     }
2412
2413
2414     /**
2415      * return recordset as a 2-dimensional array.
2416      *
2417      * @param [nRows]  is the number of rows to return. -1 means every row.
2418      *
2419      * @return an array indexed by the rows (0-based) from the recordset
2420      */
2421     function &GetArray($nRows = -1)
2422     {
2423     global $ADODB_EXTENSION; if ($ADODB_EXTENSION) return adodb_getall($this,$nRows);
2424
2425         $results = array();
2426         $cnt = 0;
2427         while (!$this->EOF && $nRows != $cnt) {
2428             $results[] = $this->fields;
2429             $this->MoveNext();
2430             $cnt++;
2431         }
2432         return $results;
2433     }
2434
2435     function &GetAll($nRows = -1)
2436     {
2437         $arr =& $this->GetArray($nRows);
2438         return $arr;
2439     }
2440
2441     /*
2442     * Some databases allow multiple recordsets to be returned. This function
2443     * will return true if there is a next recordset, or false if no more.
2444     */
2445     function NextRecordSet()
2446     {
2447         return false;
2448     }
2449
2450     /**
2451      * return recordset as a 2-dimensional array.
2452      * Helper function for ADOConnection->SelectLimit()
2453      *
2454      * @param offset    is the row to start calculations from (1-based)
2455      * @param [nrows]    is the number of rows to return
2456      *
2457      * @return an array indexed by the rows (0-based) from the recordset
2458      */
2459     function &GetArrayLimit($nrows,$offset=-1)
2460     {
2461         if ($offset <= 0) {
2462             $arr =& $this->GetArray($nrows);
2463             return $arr;
2464         }
2465
2466         $this->Move($offset);
2467
2468         $results = array();
2469         $cnt = 0;
2470         while (!$this->EOF && $nrows != $cnt) {
2471             $results[$cnt++] = $this->fields;
2472             $this->MoveNext();
2473         }
2474
2475         return $results;
2476     }
2477
2478
2479     /**
2480      * Synonym for GetArray() for compatibility with ADO.
2481      *
2482      * @param [nRows]  is the number of rows to return. -1 means every row.
2483      *
2484      * @return an array indexed by the rows (0-based) from the recordset
2485      */
2486     function &GetRows($nRows = -1)
2487     {
2488         $arr =& $this->GetArray($nRows);
2489         return $arr;
2490     }
2491
2492     /**
2493      * return whole recordset as a 2-dimensional associative array if there are more than 2 columns.
2494      * The first column is treated as the key and is not included in the array.
2495      * If there is only 2 columns, it will return a 1 dimensional array of key-value pairs unless
2496      * $force_array == true.
2497      *
2498      * @param [force_array] has only meaning if we have 2 data columns. If false, a 1 dimensional
2499      *     array is returned, otherwise a 2 dimensional array is returned. If this sounds confusing,
2500      *     read the source.
2501      *
2502      * @param [first2cols] means if there are more than 2 cols, ignore the remaining cols and
2503      * instead of returning array[col0] => array(remaining cols), return array[col0] => col1
2504      *
2505      * @return an associative array indexed by the first column of the array,
2506      *     or false if the  data has less than 2 cols.
2507      */
2508     function &GetAssoc($force_array = false, $first2cols = false) {
2509         $cols = $this->_numOfFields;
2510         if ($cols < 2) {
2511             return false;
2512         }
2513         $numIndex = isset($this->fields[0]);
2514         $results = array();
2515
2516         if (!$first2cols && ($cols > 2 || $force_array)) {
2517             if ($numIndex) {
2518                 while (!$this->EOF) {
2519                     $results[trim($this->fields[0])] = array_slice($this->fields, 1);
2520                     $this->MoveNext();
2521                 }
2522             } else {
2523                 while (!$this->EOF) {
2524                     $results[trim(reset($this->fields))] = array_slice($this->fields, 1);
2525                     $this->MoveNext();
2526                 }
2527             }
2528         } else {
2529             // return scalar values
2530             if ($numIndex) {
2531                 while (!$this->EOF) {
2532                 // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
2533                     $results[trim(($this->fields[0]))] = $this->fields[1];
2534                     $this->MoveNext();
2535                 }
2536             } else {
2537                 while (!$this->EOF) {
2538                 // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string
2539                     $v1 = trim(reset($this->fields));
2540                     $v2 = ''.next($this->fields);
2541                     $results[$v1] = $v2;
2542                     $this->MoveNext();
2543                 }
2544             }
2545         }
2546         return $results;
2547     }
2548
2549
2550     /**
2551      *
2552      * @param v      is the character timestamp in YYYY-MM-DD hh:mm:ss format
2553      * @param fmt     is the format to apply to it, using date()
2554      *
2555      * @return a timestamp formated as user desires
2556      */
2557     function UserTimeStamp($v,$fmt='Y-m-d H:i:s')
2558     {
2559         if (is_numeric($v) && strlen($v)<14) return adodb_date($fmt,$v);
2560         $tt = $this->UnixTimeStamp($v);
2561         // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
2562         if (($tt === false || $tt == -1) && $v != false) return $v;
2563         if ($tt === 0) return $this->emptyTimeStamp;
2564         return adodb_date($fmt,$tt);
2565     }
2566
2567
2568     /**
2569      * @param v      is the character date in YYYY-MM-DD format, returned by database
2570      * @param fmt     is the format to apply to it, using date()
2571      *
2572      * @return a date formated as user desires
2573      */
2574     function UserDate($v,$fmt='Y-m-d')
2575     {
2576         $tt = $this->UnixDate($v);
2577         // $tt == -1 if pre TIMESTAMP_FIRST_YEAR
2578         if (($tt === false || $tt == -1) && $v != false) return $v;
2579         else if ($tt == 0) return $this->emptyDate;
2580         else if ($tt == -1) { // pre-TIMESTAMP_FIRST_YEAR
2581         }
2582         return adodb_date($fmt,$tt);
2583
2584     }
2585
2586
2587     /**
2588      * @param $v is a date string in YYYY-MM-DD format
2589      *
2590      * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
2591      */
2592     function UnixDate($v)
2593     {
2594
2595         if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|",
2596             ($v), $rr)) return false;
2597
2598         if ($rr[1] <= TIMESTAMP_FIRST_YEAR) return 0;
2599         // h-m-s-MM-DD-YY
2600         return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
2601     }
2602
2603
2604     /**
2605      * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format
2606      *
2607      * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format
2608      */
2609     function UnixTimeStamp($v)
2610     {
2611
2612         if (!preg_match(
2613             "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
2614             ($v), $rr)) return false;
2615         if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) return 0;
2616
2617         // h-m-s-MM-DD-YY
2618         if (!isset($rr[5])) return  adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]);
2619         return  @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]);
2620     }
2621
2622
2623     /**
2624     * PEAR DB Compat - do not use internally
2625     */
2626     function Free()
2627     {
2628         return $this->Close();
2629     }
2630
2631
2632     /**
2633     * PEAR DB compat, number of rows
2634     */
2635     function NumRows()
2636     {
2637         return $this->_numOfRows;
2638     }
2639
2640
2641     /**
2642     * PEAR DB compat, number of cols
2643     */
2644     function NumCols()
2645     {
2646         return $this->_numOfFields;
2647     }
2648
2649     /**
2650     * Fetch a row, returning false if no more rows.
2651     * This is PEAR DB compat mode.
2652     *
2653     * @return false or array containing the current record
2654     */
2655     function &FetchRow()
2656     {
2657         if ($this->EOF) return false;
2658         $arr = $this->fields;
2659         $this->_currentRow++;
2660         if (!$this->_fetch()) $this->EOF = true;
2661         return $arr;
2662     }
2663
2664
2665     /**
2666     * Fetch a row, returning PEAR_Error if no more rows.
2667     * This is PEAR DB compat mode.
2668     *
2669     * @return DB_OK or error object
2670     */
2671     function FetchInto(&$arr)
2672     {
2673         if ($this->EOF) return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false;
2674         $arr = $this->fields;
2675         $this->MoveNext();
2676         return 1; // DB_OK
2677     }
2678
2679
2680     /**
2681      * Move to the first row in the recordset. Many databases do NOT support this.
2682      *
2683      * @return true or false
2684      */
2685     function MoveFirst()
2686     {
2687         if ($this->_currentRow == 0) return true;
2688         return $this->Move(0);
2689     }
2690
2691
2692     /**
2693      * Move to the last row in the recordset.
2694      *
2695      * @return true or false
2696      */
2697     function MoveLast()
2698     {
2699         if ($this->_numOfRows >= 0) return $this->Move($this->_numOfRows-1);
2700         if ($this->EOF) return false;
2701         while (!$this->EOF) {
2702             $f = $this->fields;
2703             $this->MoveNext();
2704         }
2705         $this->fields = $f;
2706         $this->EOF = false;
2707         return true;
2708     }
2709
2710
2711     /**
2712      * Move to next record in the recordset.
2713      *
2714      * @return true if there still rows available, or false if there are no more rows (EOF).
2715      */
2716     function MoveNext()
2717     {
2718         if (!$this->EOF) {
2719             $this->_currentRow++;
2720             if ($this->_fetch()) return true;
2721         }
2722         $this->EOF = true;
2723         /* -- tested error handling when scrolling cursor -- seems useless.
2724         $conn = $this->connection;
2725         if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) {
2726             $fn = $conn->raiseErrorFn;
2727             $fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database);
2728         }
2729         */
2730         return false;
2731     }
2732
2733     /**
2734      * Random access to a specific row in the recordset. Some databases do not support
2735      * access to previous rows in the databases (no scrolling backwards).
2736      *
2737      * @param rowNumber is the row to move to (0-based)
2738      *
2739      * @return true if there still rows available, or false if there are no more rows (EOF).
2740      */
2741     function Move($rowNumber = 0)
2742     {
2743         $this->EOF = false;
2744         if ($rowNumber == $this->_currentRow) return true;
2745         if ($rowNumber >= $this->_numOfRows)
2746                if ($this->_numOfRows != -1) $rowNumber = $this->_numOfRows-2;
2747
2748         if ($this->canSeek) {
2749
2750             if ($this->_seek($rowNumber)) {
2751                 $this->_currentRow = $rowNumber;
2752                 if ($this->_fetch()) {
2753                     return true;
2754                 }
2755             } else {
2756                 $this->EOF = true;
2757                 return false;
2758             }
2759         } else {
2760             if ($rowNumber < $this->_currentRow) return false;
2761             global $ADODB_EXTENSION;
2762             if ($ADODB_EXTENSION) {
2763                 while (!$this->EOF && $this->_currentRow < $rowNumber) {
2764                     adodb_movenext($this);
2765                 }
2766             } else {
2767
2768                 while (! $this->EOF && $this->_currentRow < $rowNumber) {
2769                     $this->_currentRow++;
2770
2771                     if (!$this->_fetch()) $this->EOF = true;
2772                 }
2773             }
2774             return !($this->EOF);
2775         }
2776
2777         $this->fields = false;
2778         $this->EOF = true;
2779         return false;
2780     }
2781
2782
2783     /**
2784      * Get the value of a field in the current row by column name.
2785      * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM.
2786      *
2787      * @param colname  is the field to access
2788      *
2789      * @return the value of $colname column
2790      */
2791     function Fields($colname)
2792     {
2793         return $this->fields[$colname];
2794     }
2795
2796     function GetAssocKeys($upper=true)
2797     {
2798         $this->bind = array();
2799         for ($i=0; $i < $this->_numOfFields; $i++) {
2800             $o =& $this->FetchField($i);
2801             if ($upper === 2) $this->bind[$o->name] = $i;
2802             else $this->bind[($upper) ? strtoupper($o->name) : strtolower($o->name)] = $i;
2803         }
2804     }
2805
2806   /**
2807    * Use associative array to get fields array for databases that do not support
2808    * associative arrays. Submitted by Paolo S. Asioli paolo.asioli@libero.it
2809    *
2810    * If you don't want uppercase cols, set $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC
2811    * before you execute your SQL statement, and access $rs->fields['col'] directly.
2812    *
2813    * $upper  0 = lowercase, 1 = uppercase, 2 = whatever is returned by FetchField
2814    */
2815     function &GetRowAssoc($upper=1)
2816     {
2817         $record = array();
2818      //    if (!$this->fields) return $record;
2819
2820            if (!$this->bind) {
2821             $this->GetAssocKeys($upper);
2822         }
2823
2824         foreach($this->bind as $k => $v) {
2825             $record[$k] = $this->fields[$v];
2826         }
2827
2828         return $record;
2829     }
2830
2831
2832     /**
2833      * Clean up recordset
2834      *
2835      * @return true or false
2836      */
2837     function Close()
2838     {
2839         // free connection object - this seems to globally free the object
2840         // and not merely the reference, so don't do this...
2841         // $this->connection = false;
2842         if (!$this->_closed) {
2843             $this->_closed = true;
2844             return $this->_close();
2845         } else
2846             return true;
2847     }
2848
2849     /**
2850      * synonyms RecordCount and RowCount
2851      *
2852      * @return the number of rows or -1 if this is not supported
2853      */
2854     function RecordCount() {return $this->_numOfRows;}
2855
2856
2857     /*
2858     * If we are using PageExecute(), this will return the maximum possible rows
2859     * that can be returned when paging a recordset.
2860     */
2861     function MaxRecordCount()
2862     {
2863         return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount();
2864     }
2865
2866     /**
2867      * synonyms RecordCount and RowCount
2868      *
2869      * @return the number of rows or -1 if this is not supported
2870      */
2871     function RowCount() {return $this->_numOfRows;}
2872
2873
2874      /**
2875      * Portable RecordCount. Pablo Roca <pabloroca@mvps.org>
2876      *
2877      * @return the number of records from a previous SELECT. All databases support this.
2878      *
2879      * But aware possible problems in multiuser environments. For better speed the table
2880      * must be indexed by the condition. Heavy test this before deploying.
2881      */
2882     function PO_RecordCount($table="", $condition="") {
2883
2884         $lnumrows = $this->_numOfRows;
2885         // the database doesn't support native recordcount, so we do a workaround
2886         if ($lnumrows == -1 && $this->connection) {
2887             IF ($table) {
2888                 if ($condition) $condition = " WHERE " . $condition;
2889                 $resultrows = &$this->connection->Execute("SELECT COUNT(*) FROM $table $condition");
2890                 if ($resultrows) $lnumrows = reset($resultrows->fields);
2891             }
2892         }
2893         return $lnumrows;
2894     }
2895
2896     /**
2897      * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
2898      */
2899     function CurrentRow() {return $this->_currentRow;}
2900
2901     /**
2902      * synonym for CurrentRow -- for ADO compat
2903      *
2904      * @return the current row in the recordset. If at EOF, will return the last row. 0-based.
2905      */
2906     function AbsolutePosition() {return $this->_currentRow;}
2907
2908     /**
2909      * @return the number of columns in the recordset. Some databases will set this to 0
2910      * if no records are returned, others will return the number of columns in the query.
2911      */
2912     function FieldCount() {return $this->_numOfFields;}
2913
2914
2915     /**
2916      * Get the ADOFieldObject of a specific column.
2917      *
2918      * @param fieldoffset    is the column position to access(0-based).
2919      *
2920      * @return the ADOFieldObject for that column, or false.
2921      */
2922     function &FetchField($fieldoffset)
2923     {
2924         // must be defined by child class
2925     }
2926
2927     /**
2928      * Get the ADOFieldObjects of all columns in an array.
2929      *
2930      */
2931     function FieldTypesArray()
2932     {
2933         $arr = array();
2934         for ($i=0, $max=$this->_numOfFields; $i < $max; $i++)
2935             $arr[] = $this->FetchField($i);
2936         return $arr;
2937     }
2938
2939     /**
2940     * Return the fields array of the current row as an object for convenience.
2941     * The default case is lowercase field names.
2942     *
2943     * @return the object with the properties set to the fields of the current row
2944     */
2945     function &FetchObj()
2946     {
2947         $o =& $this->FetchObject(false);
2948         return $o;
2949     }
2950
2951     /**
2952     * Return the fields array of the current row as an object for convenience.
2953     * The default case is uppercase.
2954     *
2955     * @param $isupper to set the object property names to uppercase
2956     *
2957     * @return the object with the properties set to the fields of the current row
2958     */
2959     function &FetchObject($isupper=true)
2960     {
2961         if (empty($this->_obj)) {
2962             $this->_obj =& new ADOFetchObj();
2963             $this->_names = array();
2964             for ($i=0; $i <$this->_numOfFields; $i++) {
2965                 $f = $this->FetchField($i);
2966                 $this->_names[] = $f->name;
2967             }
2968         }
2969         $i = 0;
2970         $o = &$this->_obj;
2971         for ($i=0; $i <$this->_numOfFields; $i++) {
2972             $name = $this->_names[$i];
2973             if ($isupper) $n = strtoupper($name);
2974             else $n = $name;
2975
2976             $o->$n = $this->Fields($name);
2977         }
2978         return $o;
2979     }
2980
2981     /**
2982     * Return the fields array of the current row as an object for convenience.
2983     * The default is lower-case field names.
2984     *
2985     * @return the object with the properties set to the fields of the current row,
2986     *     or false if EOF
2987     *
2988     * Fixed bug reported by tim@orotech.net
2989     */
2990     function &FetchNextObj()
2991     {
2992         return $this->FetchNextObject(false);
2993     }
2994
2995
2996     /**
2997     * Return the fields array of the current row as an object for convenience.
2998     * The default is upper case field names.
2999     *
3000     * @param $isupper to set the object property names to uppercase
3001     *
3002     * @return the object with the properties set to the fields of the current row,
3003     *     or false if EOF
3004     *
3005     * Fixed bug reported by tim@orotech.net
3006     */
3007     function &FetchNextObject($isupper=true)
3008     {
3009         $o = false;
3010         if ($this->_numOfRows != 0 && !$this->EOF) {
3011             $o = $this->FetchObject($isupper);
3012             $this->_currentRow++;
3013             if ($this->_fetch()) return $o;
3014         }
3015         $this->EOF = true;
3016         return $o;
3017     }
3018
3019     /**
3020      * Get the metatype of the column. This is used for formatting. This is because
3021      * many databases use different names for the same type, so we transform the original
3022      * type to our standardised version which uses 1 character codes:
3023      *
3024      * @param t  is the type passed in. Normally is ADOFieldObject->type.
3025      * @param len is the maximum length of that field. This is because we treat character
3026      *     fields bigger than a certain size as a 'B' (blob).
3027      * @param fieldobj is the field object returned by the database driver. Can hold
3028      *    additional info (eg. primary_key for mysql).
3029      *
3030      * @return the general type of the data:
3031      *    C for character < 200 chars
3032      *    X for teXt (>= 200 chars)
3033      *    B for Binary
3034      *     N for numeric floating point
3035      *    D for date
3036      *    T for timestamp
3037      *     L for logical/Boolean
3038      *    I for integer
3039      *    R for autoincrement counter/integer
3040      *
3041      *
3042     */
3043     function MetaType($t,$len=-1,$fieldobj=false)
3044     {
3045         if (is_object($t)) {
3046             $fieldobj = $t;
3047             $t = $fieldobj->type;
3048             $len = $fieldobj->max_length;
3049         }
3050     // changed in 2.32 to hashing instead of switch stmt for speed...
3051     static $typeMap = array(
3052         'VARCHAR' => 'C',
3053         'VARCHAR2' => 'C',
3054         'CHAR' => 'C',
3055         'C' => 'C',
3056         'STRING' => 'C',
3057         'NCHAR' => 'C',
3058         'NVARCHAR' => 'C',
3059         'VARYING' => 'C',
3060         'BPCHAR' => 'C',
3061         'CHARACTER' => 'C',
3062         'INTERVAL' => 'C',  # Postgres
3063         ##
3064         'LONGCHAR' => 'X',
3065         'TEXT' => 'X',
3066         'NTEXT' => 'X',
3067         'M' => 'X',
3068         'X' => 'X',
3069         'CLOB' => 'X',
3070         'NCLOB' => 'X',
3071         'LVARCHAR' => 'X',
3072         ##
3073         'BLOB' => 'B',
3074         'IMAGE' => 'B',
3075         'BINARY' => 'B',
3076         'VARBINARY' => 'B',
3077         'LONGBINARY' => 'B',
3078         'B' => 'B',
3079         ##
3080         'YEAR' => 'D', // mysql
3081         'DATE' => 'D',
3082         'D' => 'D',
3083         ##
3084         'TIME' => 'T',
3085         'TIMESTAMP' => 'T',
3086         'DATETIME' => 'T',
3087         'TIMESTAMPTZ' => 'T',
3088         'T' => 'T',
3089         ##
3090         'BOOL' => 'L',
3091         'BOOLEAN' => 'L',
3092         'BIT' => 'L',
3093         'L' => 'L',
3094         ##
3095         'COUNTER' => 'R',
3096         'R' => 'R',
3097         'SERIAL' => 'R', // ifx
3098         'INT IDENTITY' => 'R',
3099         ##
3100         'INT' => 'I',
3101         'INTEGER' => 'I',
3102         'INTEGER UNSIGNED' => 'I',
3103         'SHORT' => 'I',
3104         'TINYINT' => 'I',
3105         'SMALLINT' => 'I',
3106         'I' => 'I',
3107         ##
3108         'LONG' => 'N', // interbase is numeric, oci8 is blob
3109         'BIGINT' => 'N', // this is bigger than PHP 32-bit integers
3110         'DECIMAL' => 'N',
3111         'DEC' => 'N',
3112         'REAL' => 'N',
3113         'DOUBLE' => 'N',
3114         'DOUBLE PRECISION' => 'N',
3115         'SMALLFLOAT' => 'N',
3116         'FLOAT' => 'N',
3117         'NUMBER' => 'N',
3118         'NUM' => 'N',
3119         'NUMERIC' => 'N',
3120         'MONEY' => 'N',
3121
3122         ## informix 9.2
3123         'SQLINT' => 'I',
3124         'SQLSERIAL' => 'I',
3125         'SQLSMINT' => 'I',
3126         'SQLSMFLOAT' => 'N',
3127         'SQLFLOAT' => 'N',
3128         'SQLMONEY' => 'N',
3129         'SQLDECIMAL' => 'N',
3130         'SQLDATE' => 'D',
3131         'SQLVCHAR' => 'C',
3132         'SQLCHAR' => 'C',
3133         'SQLDTIME' => 'T',
3134         'SQLINTERVAL' => 'N',
3135         'SQLBYTES' => 'B',
3136         'SQLTEXT' => 'X'
3137         );
3138
3139         $tmap = false;
3140         $t = strtoupper($t);
3141         $tmap = @$typeMap[$t];
3142         switch ($tmap) {
3143         case 'C':
3144
3145             // is the char field is too long, return as text field...
3146             if ($this->blobSize >= 0) {
3147                 if ($len > $this->blobSize) return 'X';
3148             } elseif ($len > 250) {
3149                 return 'X';
3150             }
3151             return 'C';
3152
3153         case 'I':
3154             if (!empty($fieldobj->primary_key)) return 'R';
3155             return 'I';
3156
3157         case false:
3158             return 'N';
3159
3160         case 'B':
3161              if (isset($fieldobj->binary))
3162                  return ($fieldobj->binary) ? 'B' : 'X';
3163             return 'B';
3164
3165         case 'D':
3166             if (!empty($this->datetime)) return 'T';
3167             return 'D';
3168
3169         default:
3170             if ($t == 'LONG' && $this->dataProvider == 'oci8') return 'B';
3171             return $tmap;
3172         }
3173     }
3174
3175     function _close() {}
3176
3177     /**
3178      * set/returns the current recordset page when paginating
3179      */
3180     function AbsolutePage($page=-1)
3181     {
3182         if ($page != -1) $this->_currentPage = $page;
3183         return $this->_currentPage;
3184     }
3185
3186     /**
3187      * set/returns the status of the atFirstPage flag when paginating
3188      */
3189     function AtFirstPage($status=false)
3190     {
3191         if ($status != false) $this->_atFirstPage = $status;
3192         return $this->_atFirstPage;
3193     }
3194
3195     function LastPageNo($page = false)
3196     {
3197         if ($page != false) $this->_lastPageNo = $page;
3198         return $this->_lastPageNo;
3199     }
3200
3201     /**
3202      * set/returns the status of the atLastPage flag when paginating
3203      */
3204     function AtLastPage($status=false)
3205     {
3206         if ($status != false) $this->_atLastPage = $status;
3207         return $this->_atLastPage;
3208     }
3209
3210 } // end class ADORecordSet
3211
3212     //==============================================================================================
3213     // CLASS ADORecordSet_array
3214     //==============================================================================================
3215
3216     /**
3217      * This class encapsulates the concept of a recordset created in memory
3218      * as an array. This is useful for the creation of cached recordsets.
3219      *
3220      * Note that the constructor is different from the standard ADORecordSet
3221      */
3222
3223     class ADORecordSet_array extends ADORecordSet
3224     {
3225         var $databaseType = 'array';
3226
3227         var $_array;     // holds the 2-dimensional data array
3228         var $_types;    // the array of types of each column (C B I L M)
3229         var $_colnames;    // names of each column in array
3230         var $_skiprow1;    // skip 1st row because it holds column names
3231         var $_fieldarr; // holds array of field objects
3232         var $canSeek = true;
3233         var $affectedrows = false;
3234         var $insertid = false;
3235         var $sql = '';
3236         var $compat = false;
3237         /**
3238          * Constructor
3239          *
3240          */
3241         function ADORecordSet_array($fakeid=1)
3242         {
3243         global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH;
3244
3245             // fetch() on EOF does not delete $this->fields
3246             $this->compat = !empty($ADODB_COMPAT_FETCH);
3247             $this->ADORecordSet($fakeid); // fake queryID
3248             $this->fetchMode = $ADODB_FETCH_MODE;
3249         }
3250
3251
3252         /**
3253          * Setup the array.
3254          *
3255          * @param array        is a 2-dimensional array holding the data.
3256          *            The first row should hold the column names
3257          *            unless paramter $colnames is used.
3258          * @param typearr    holds an array of types. These are the same types
3259          *            used in MetaTypes (C,B,L,I,N).
3260          * @param [colnames]    array of column names. If set, then the first row of
3261          *            $array should not hold the column names.
3262          */
3263         function InitArray($array,$typearr,$colnames=false)
3264         {
3265             $this->_array = $array;
3266             $this->_types = $typearr;
3267             if ($colnames) {
3268                 $this->_skiprow1 = false;
3269                 $this->_colnames = $colnames;
3270             } else  {
3271                 $this->_skiprow1 = true;
3272                 $this->_colnames = $array[0];
3273             }
3274             $this->Init();
3275         }
3276         /**
3277          * Setup the Array and datatype file objects
3278          *
3279          * @param array        is a 2-dimensional array holding the data.
3280          *            The first row should hold the column names
3281          *            unless paramter $colnames is used.
3282          * @param fieldarr    holds an array of ADOFieldObject's.
3283          */
3284         function InitArrayFields(&$array,&$fieldarr)
3285         {
3286             $this->_array =& $array;
3287             $this->_skiprow1= false;
3288             if ($fieldarr) {
3289                 $this->_fieldobjects =& $fieldarr;
3290             }
3291             $this->Init();
3292         }
3293
3294         function &GetArray($nRows=-1)
3295         {
3296             if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) {
3297                 return $this->_array;
3298             } else {
3299                 $arr =& ADORecordSet::GetArray($nRows);
3300                 return $arr;
3301             }
3302         }
3303
3304         function _initrs()
3305         {
3306             $this->_numOfRows =  sizeof($this->_array);
3307             if ($this->_skiprow1) $this->_numOfRows -= 1;
3308
3309             $this->_numOfFields =(isset($this->_fieldobjects)) ?
3310                  sizeof($this->_fieldobjects):sizeof($this->_types);
3311         }
3312
3313         /* Use associative array to get fields array */
3314         function Fields($colname)
3315         {
3316             if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname];
3317
3318             if (!$this->bind) {
3319                 $this->bind = array();
3320                 for ($i=0; $i < $this->_numOfFields; $i++) {
3321                     $o = $this->FetchField($i);
3322                     $this->bind[strtoupper($o->name)] = $i;
3323                 }
3324             }
3325             return $this->fields[$this->bind[strtoupper($colname)]];
3326         }
3327
3328         function &FetchField($fieldOffset = -1)
3329         {
3330             if (isset($this->_fieldobjects)) {
3331                 return $this->_fieldobjects[$fieldOffset];
3332             }
3333             $o =  new ADOFieldObject();
3334             $o->name = $this->_colnames[$fieldOffset];
3335             $o->type =  $this->_types[$fieldOffset];
3336             $o->max_length = -1; // length not known
3337
3338             return $o;
3339         }
3340
3341         function _seek($row)
3342         {
3343             if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) {
3344                 $this->_currentRow = $row;
3345                 if ($this->_skiprow1) $row += 1;
3346                 $this->fields = $this->_array[$row];
3347                 return true;
3348             }
3349             return false;
3350         }
3351
3352         function MoveNext()
3353         {
3354             if (!$this->EOF) {
3355                 $this->_currentRow++;
3356
3357                 $pos = $this->_currentRow;
3358
3359                 if ($this->_numOfRows <= $pos) {
3360                     if (!$this->compat) $this->fields = false;
3361                 } else {
3362                     if ($this->_skiprow1) $pos += 1;
3363                     $this->fields = $this->_array[$pos];
3364                     return true;
3365                 }
3366                 $this->EOF = true;
3367             }
3368
3369             return false;
3370         }
3371
3372         function _fetch()
3373         {
3374             $pos = $this->_currentRow;
3375
3376             if ($this->_numOfRows <= $pos) {
3377                 if (!$this->compat) $this->fields = false;
3378                 return false;
3379             }
3380             if ($this->_skiprow1) $pos += 1;
3381             $this->fields = $this->_array[$pos];
3382             return true;
3383         }
3384
3385         function _close()
3386         {
3387             return true;
3388         }
3389
3390     } // ADORecordSet_array
3391
3392     //==============================================================================================
3393     // HELPER FUNCTIONS
3394     //==============================================================================================
3395
3396     /**
3397      * Synonym for ADOLoadCode. Private function. Do not use.
3398      *
3399      * @deprecated
3400      */
3401     function ADOLoadDB($dbType)
3402     {
3403         return ADOLoadCode($dbType);
3404     }
3405
3406     /**
3407      * Load the code for a specific database driver. Private function. Do not use.
3408      */
3409     function ADOLoadCode($dbType)
3410     {
3411     global $ADODB_LASTDB;
3412
3413         if (!$dbType) return false;
3414         $db = strtolower($dbType);
3415         switch ($db) {
3416             case 'maxsql': $db = 'mysqlt'; break;
3417             case 'postgres':
3418             case 'pgsql': $db = 'postgres7'; break;
3419         }
3420         @include_once(ADODB_DIR."/drivers/adodb-".$db.".inc.php");
3421         $ADODB_LASTDB = $db;
3422
3423         $ok = class_exists("ADODB_" . $db);
3424         if ($ok) return $db;
3425
3426         $file = ADODB_DIR."/drivers/adodb-".$db.".inc.php";
3427         if (!file_exists($file)) ADOConnection::outp("Missing file: $file");
3428         else ADOConnection::outp("Syntax error in file: $file");
3429         return false;
3430     }
3431
3432     /**
3433      * synonym for ADONewConnection for people like me who cannot remember the correct name
3434      */
3435     function &NewADOConnection($db='')
3436     {
3437         $tmp =& ADONewConnection($db);
3438         return $tmp;
3439     }
3440
3441     /**
3442      * Instantiate a new Connection class for a specific database driver.
3443      *
3444      * @param [db]  is the database Connection object to create. If undefined,
3445      *     use the last database driver that was loaded by ADOLoadCode().
3446      *
3447      * @return the freshly created instance of the Connection class.
3448      */
3449     function &ADONewConnection($db='')
3450     {
3451     GLOBAL $ADODB_NEWCONNECTION, $ADODB_LASTDB;
3452
3453         if (!defined('ADODB_ASSOC_CASE')) define('ADODB_ASSOC_CASE',2);
3454         $errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false;
3455
3456         if (!empty($ADODB_NEWCONNECTION)) {
3457             $obj = $ADODB_NEWCONNECTION($db);
3458             if ($obj) {
3459                 if ($errorfn)  $obj->raiseErrorFn = $errorfn;
3460                 return $obj;
3461             }
3462         }
3463
3464         if (!isset($ADODB_LASTDB)) $ADODB_LASTDB = '';
3465         if (empty($db)) $db = $ADODB_LASTDB;
3466
3467         if ($db != $ADODB_LASTDB) $db = ADOLoadCode($db);
3468
3469         if (!$db) {
3470              if ($errorfn) {
3471                 // raise an error
3472                 $ignore = false;
3473                 $errorfn('ADONewConnection', 'ADONewConnection', -998,
3474                          "could not load the database driver for '$db",
3475                          $db,false,$ignore);
3476             } else
3477                  ADOConnection::outp( "<p>ADONewConnection: Unable to load database driver '$db'</p>",false);
3478
3479             return false;
3480         }
3481
3482         $cls = 'ADODB_'.$db;
3483         if (!class_exists($cls)) {
3484             adodb_backtrace();
3485             return false;
3486         }
3487
3488         $obj =& new $cls();
3489         if ($errorfn) $obj->raiseErrorFn = $errorfn;
3490
3491         return $obj;
3492     }
3493
3494     // $perf == true means called by NewPerfMonitor()
3495     function _adodb_getdriver($provider,$drivername,$perf=false)
3496     {
3497         if ($provider !== 'native' && $provider != 'odbc' && $provider != 'ado')
3498             $drivername = $provider;
3499         else {
3500             if (substr($drivername,0,5) == 'odbc_') $drivername = substr($drivername,5);
3501             else if (substr($drivername,0,4) == 'ado_') $drivername = substr($drivername,4);
3502             else
3503             switch($drivername) {
3504             case 'oracle': $drivername = 'oci8';break;
3505             //case 'sybase': $drivername = 'mssql';break;
3506             case 'access':
3507                         if ($perf) $drivername = '';
3508                         break;
3509             case 'db2':
3510                         break;
3511             default:
3512                 $drivername = 'generic';
3513                 break;
3514             }
3515         }
3516
3517         return $drivername;
3518     }
3519
3520     function &NewPerfMonitor(&$conn)
3521     {
3522         $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType,true);
3523         if (!$drivername || $drivername == 'generic') return false;
3524         include_once(ADODB_DIR.'/adodb-perf.inc.php');
3525         @include_once(ADODB_DIR."/perf/perf-$drivername.inc.php");
3526         $class = "Perf_$drivername";
3527         if (!class_exists($class)) return false;
3528         $perf =& new $class($conn);
3529
3530         return $perf;
3531     }
3532
3533     function &NewDataDictionary(&$conn)
3534     {
3535         $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType);
3536
3537         include_once(ADODB_DIR.'/adodb-lib.inc.php');
3538         include_once(ADODB_DIR.'/adodb-datadict.inc.php');
3539         $path = ADODB_DIR."/datadict/datadict-$drivername.inc.php";
3540
3541         if (!file_exists($path)) {
3542             ADOConnection::outp("Database driver '$path' not available");
3543             return false;
3544         }
3545         include_once($path);
3546         $class = "ADODB2_$drivername";
3547         $dict =& new $class();
3548         $dict->dataProvider = $conn->dataProvider;
3549         $dict->connection = &$conn;
3550         $dict->upperName = strtoupper($drivername);
3551         $dict->quote = $conn->nameQuote;
3552         if (is_resource($conn->_connectionID))
3553             $dict->serverInfo = $conn->ServerInfo();
3554
3555         return $dict;
3556     }
3557
3558
3559     /**
3560     * Save a file $filename and its $contents (normally for caching) with file locking
3561     */
3562     function adodb_write_file($filename, $contents,$debug=false)
3563     {
3564     # http://www.php.net/bugs.php?id=9203 Bug that flock fails on Windows
3565     # So to simulate locking, we assume that rename is an atomic operation.
3566     # First we delete $filename, then we create a $tempfile write to it and
3567     # rename to the desired $filename. If the rename works, then we successfully
3568     # modified the file exclusively.
3569     # What a stupid need - having to simulate locking.
3570     # Risks:
3571     # 1. $tempfile name is not unique -- very very low
3572     # 2. unlink($filename) fails -- ok, rename will fail
3573     # 3. adodb reads stale file because unlink fails -- ok, $rs timeout occurs
3574     # 4. another process creates $filename between unlink() and rename() -- ok, rename() fails and  cache updated
3575         if (strncmp(PHP_OS,'WIN',3) === 0) {
3576             // skip the decimal place
3577             $mtime = substr(str_replace(' ','_',microtime()),2);
3578             // getmypid() actually returns 0 on Win98 - never mind!
3579             $tmpname = $filename.uniqid($mtime).getmypid();
3580             if (!($fd = fopen($tmpname,'a'))) return false;
3581             $ok = ftruncate($fd,0);
3582             if (!fwrite($fd,$contents)) $ok = false;
3583             fclose($fd);
3584             chmod($tmpname,0644);
3585             // the tricky moment
3586             @unlink($filename);
3587             if (!@rename($tmpname,$filename)) {
3588                 unlink($tmpname);
3589                 $ok = false;
3590             }
3591             if (!$ok) {
3592                 if ($debug) ADOConnection::outp( " Rename $tmpname ".($ok? 'ok' : 'failed'));
3593             }
3594             return $ok;
3595         }
3596         if (!($fd = fopen($filename, 'a'))) return false;
3597         if (flock($fd, LOCK_EX) && ftruncate($fd, 0)) {
3598             $ok = fwrite( $fd, $contents );
3599             fclose($fd);
3600             chmod($filename,0644);
3601         }else {
3602             fclose($fd);
3603             if ($debug)ADOConnection::outp( " Failed acquiring lock for $filename<br>\n");
3604             $ok = false;
3605         }
3606
3607         return $ok;
3608     }
3609
3610     /*
3611         Perform a print_r, with pre tags for better formatting.
3612     */
3613     function adodb_pr($var)
3614     {
3615         if (isset($_SERVER['HTTP_USER_AGENT'])) {
3616             echo " <pre>\n";print_r($var);echo "</pre>\n";
3617         } else
3618             print_r($var);
3619     }
3620
3621     /*
3622         Perform a stack-crawl and pretty print it.
3623
3624         @param printOrArr  Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then).
3625         @param levels Number of levels to display
3626     */
3627     function adodb_backtrace($printOrArr=true,$levels=9999)
3628     {
3629         $s = '';
3630         if (PHPVERSION() < 4.3) return;
3631
3632         $html =  (isset($_SERVER['HTTP_USER_AGENT']));
3633         $fmt =  ($html) ? "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>" : "%% line %4d, file: %s";
3634
3635         $MAXSTRLEN = 64;
3636
3637         $s = ($html) ? '<pre align=left>' : '';
3638
3639         if (is_array($printOrArr)) $traceArr = $printOrArr;
3640         else $traceArr = debug_backtrace();
3641         array_shift($traceArr);
3642         $tabs = sizeof($traceArr)-1;
3643
3644         foreach ($traceArr as $arr) {
3645             $levels -= 1;
3646             if ($levels < 0) break;
3647
3648             $args = array();
3649             for ($i=0; $i < $tabs; $i++) $s .=  ($html) ? ' &nbsp; ' : "\t";
3650             $tabs -= 1;
3651             if ($html) $s .= '<font face="Courier New,Courier">';
3652             if (isset($arr['class'])) $s .= $arr['class'].'.';
3653             if (isset($arr['args']))
3654              foreach($arr['args'] as $v) {
3655                 if (is_null($v)) $args[] = 'null';
3656                 else if (is_array($v)) $args[] = 'Array['.sizeof($v).']';
3657                 else if (is_object($v)) $args[] = 'Object:'.get_class($v);
3658                 else if (is_bool($v)) $args[] = $v ? 'true' : 'false';
3659                 else {
3660                     $v = (string) @$v;
3661                     $str = htmlspecialchars(substr($v,0,$MAXSTRLEN));
3662                     if (strlen($v) > $MAXSTRLEN) $str .= '...';
3663                     $args[] = $str;
3664                 }
3665             }
3666             $s .= $arr['function'].'('.implode(', ',$args).')';
3667
3668
3669             $s .= @sprintf($fmt, $arr['line'],$arr['file'],basename($arr['file']));
3670
3671             $s .= "\n";
3672         }
3673         if ($html) $s .= '</pre>';
3674         if ($printOrArr) print $s;
3675
3676         return $s;
3677     }
3678
3679 }
3680
3681 // Local Variables:
3682 // mode: php
3683 // tab-width: 8
3684 // c-basic-offset: 4
3685 // c-hanging-comment-ender-p: nil
3686 // indent-tabs-mode: nil
3687 // End:
3688 ?>