]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/adodb.inc.php
Harmonize file footer
[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                 } else if (strnatcmp(PHP_VERSION,'4.2.0')>=0) {
96                         define('ADODB_PHPVER',0x4200);
97                 } else if (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                 } else if (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                         } else if (!$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                                         } else if ($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                         } else if ($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: 4
3684 // c-basic-offset: 4
3685 // c-hanging-comment-ender-p: nil
3686 // indent-tabs-mode: nil
3687 // End:
3688 ?>