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